const DEFAULT_WIDTH = 1280;
const DEFAULT_HEIGHT = 720;
const DEFAULT_FPS = 30;
const MIME_TYPE_CANDIDATES = [
    { mimeType: 'video/mp4;codecs=avc1.42E01E,mp4a.40.2', extension: 'mp4' },
    { mimeType: 'video/mp4', extension: 'mp4' },
    { mimeType: 'video/webm;codecs=vp9,opus', extension: 'webm' },
    { mimeType: 'video/webm;codecs=vp8,opus', extension: 'webm' },
    { mimeType: 'video/webm', extension: 'webm' }
];

function getSupportedFormat(mediaRecorderCtor) {
    if (!mediaRecorderCtor || typeof mediaRecorderCtor.isTypeSupported !== 'function') {
        return {
            mimeType: '',
            extension: 'webm'
        };
    }

    return MIME_TYPE_CANDIDATES.find(format => mediaRecorderCtor.isTypeSupported(format.mimeType)) || {
        mimeType: '',
        extension: 'webm'
    };
}

function isElementVisible(element) {
    if (!element || element.classList.contains('hidden')) {
        return false;
    }

    const rect = element.getBoundingClientRect();
    return rect.width > 0 && rect.height > 0;
}

function drawVideoCover(context, video, x, y, width, height) {
    const videoWidth = video.videoWidth || width;
    const videoHeight = video.videoHeight || height;
    const sourceRatio = videoWidth / videoHeight;
    const targetRatio = width / height;
    let sourceX = 0;
    let sourceY = 0;
    let sourceWidth = videoWidth;
    let sourceHeight = videoHeight;

    if (sourceRatio > targetRatio) {
        sourceWidth = videoHeight * targetRatio;
        sourceX = (videoWidth - sourceWidth) / 2;
    } else {
        sourceHeight = videoWidth / targetRatio;
        sourceY = (videoHeight - sourceHeight) / 2;
    }

    context.drawImage(video, sourceX, sourceY, sourceWidth, sourceHeight, x, y, width, height);
}

function drawEmptyFrame(context, canvas) {
    context.fillStyle = '#111827';
    context.fillRect(0, 0, canvas.width, canvas.height);
    context.fillStyle = '#9ca3af';
    context.font = '24px sans-serif';
    context.textAlign = 'center';
    context.textBaseline = 'middle';
    context.fillText('等待会议画面...', canvas.width / 2, canvas.height / 2);
}

function drawGrid(context, videos, canvas) {
    const columns = Math.ceil(Math.sqrt(videos.length));
    const rows = Math.ceil(videos.length / columns);
    const gap = 8;
    const tileWidth = (canvas.width - gap * (columns - 1)) / columns;
    const tileHeight = (canvas.height - gap * (rows - 1)) / rows;

    videos.forEach((video, index) => {
        const column = index % columns;
        const row = Math.floor(index / columns);
        const x = column * (tileWidth + gap);
        const y = row * (tileHeight + gap);
        drawVideoCover(context, video, x, y, tileWidth, tileHeight);
    });
}

function drawLocalPreview(context, localVideo, canvas) {
    const previewWidth = Math.floor(canvas.width * 0.22);
    const previewHeight = Math.floor(previewWidth * 9 / 16);
    const margin = 24;
    const x = canvas.width - previewWidth - margin;
    const y = canvas.height - previewHeight - margin;

    context.fillStyle = 'rgba(0, 0, 0, 0.4)';
    context.fillRect(x - 4, y - 4, previewWidth + 8, previewHeight + 8);
    drawVideoCover(context, localVideo, x, y, previewWidth, previewHeight);
}

function collectStreams({ localStream, remoteStream, remoteStreams } = {}) {
    return [
        localStream,
        remoteStream,
        ...Object.values(remoteStreams || {})
    ].filter(Boolean);
}

function collectLiveAudioTracks(streams) {
    return streams.flatMap(stream => stream.getAudioTracks())
        .filter(track => track.readyState !== 'ended');
}

export class MeetingRecorder {
    constructor({
        documentRef = document,
        windowRef = window,
        width = DEFAULT_WIDTH,
        height = DEFAULT_HEIGHT,
        fps = DEFAULT_FPS
    } = {}) {
        this.document = documentRef;
        this.window = windowRef;
        this.width = width;
        this.height = height;
        this.fps = fps;
        this.mediaRecorder = null;
        this.chunks = [];
        this.animationFrameId = null;
        this.audioContext = null;
        this.audioSources = [];
        this.recordingStream = null;
        this.connectionId = '';
    }

    isSupported() {
        return Boolean(
            this.window.MediaRecorder &&
            this.document.createElement('canvas').captureStream
        );
    }

    isRecording() {
        return Boolean(this.mediaRecorder && this.mediaRecorder.state !== 'inactive');
    }

    async start({ localStream, remoteStream, remoteStreams, connectionId } = {}) {
        if (this.isRecording()) {
            throw new Error('会议正在录制中');
        }

        if (!this.isSupported()) {
            throw new Error('当前浏览器不支持会议录制');
        }

        const canvas = this.document.createElement('canvas');
        canvas.width = this.width;
        canvas.height = this.height;
        const context = canvas.getContext('2d');

        if (!context) {
            throw new Error('无法创建录制画布');
        }

        this.connectionId = connectionId || '';
        this.chunks = [];
        this.canvas = canvas;
        this.context = context;

        const canvasStream = canvas.captureStream(this.fps);
        const streams = collectStreams({ localStream, remoteStream, remoteStreams });
        const audioTrack = this.createMixedAudioTrack(streams);
        const tracks = [
            ...canvasStream.getVideoTracks(),
            ...(audioTrack ? [audioTrack] : [])
        ];

        this.recordingStream = new this.window.MediaStream(tracks);
        try {
            this.startDrawing();
            this.startMediaRecorder(this.recordingStream);
        }
        catch (error) {
            this.cleanup();
            throw error;
        }
    }

    stop() {
        if (!this.isRecording()) {
            return Promise.resolve(null);
        }

        return new Promise((resolve, reject) => {
            this.pendingStop = { resolve, reject };
            this.mediaRecorder.stop();
        });
    }

    createMixedAudioTrack(streams) {
        const audioTracks = collectLiveAudioTracks(streams);

        if (audioTracks.length === 0) {
            return null;
        }

        const AudioContextCtor = this.window.AudioContext || this.window.webkitAudioContext;
        if (!AudioContextCtor) {
            return audioTracks[0].clone ? audioTracks[0].clone() : audioTracks[0];
        }

        this.audioContext = new AudioContextCtor();
        const destination = this.audioContext.createMediaStreamDestination();

        audioTracks.forEach(track => {
            const sourceStream = new this.window.MediaStream([track]);
            const source = this.audioContext.createMediaStreamSource(sourceStream);
            source.connect(destination);
            this.audioSources.push(source);
        });

        return destination.stream.getAudioTracks()[0] || null;
    }

    startMediaRecorder(stream) {
        const MediaRecorderCtor = this.window.MediaRecorder;
        const format = getSupportedFormat(MediaRecorderCtor);
        const options = format.mimeType ? { mimeType: format.mimeType } : {};
        this.fileExtension = format.extension;

        this.mediaRecorder = new MediaRecorderCtor(stream, options);
        this.mediaRecorder.ondataavailable = (event) => {
            if (event.data && event.data.size > 0) {
                this.chunks.push(event.data);
            }
        };
        this.mediaRecorder.onerror = (event) => {
            if (this.pendingStop) {
                this.pendingStop.reject(event.error || new Error('录制失败'));
                this.pendingStop = null;
            }
            this.cleanup();
        };
        this.mediaRecorder.onstop = () => {
            const blob = new Blob(this.chunks, { type: this.mediaRecorder.mimeType || 'video/webm' });
            const filename = this.buildFilename();
            const mimeType = blob.type || this.mediaRecorder.mimeType || 'video/webm';
            this.cleanup();
            if (this.pendingStop) {
                this.pendingStop.resolve({ blob, filename, mimeType });
                this.pendingStop = null;
            }
        };

        this.mediaRecorder.start(1000);
    }

    startDrawing() {
        const draw = () => {
            this.drawFrame();
            this.animationFrameId = this.window.requestAnimationFrame(draw);
        };

        draw();
    }

    drawFrame() {
        const context = this.context;
        const canvas = this.canvas;
        const videos = this.getRecordableVideos();
        const localVideo = videos.find(video => video.id === 'localVideo');
        const remoteVideos = videos.filter(video => video !== localVideo);

        context.fillStyle = '#020617';
        context.fillRect(0, 0, canvas.width, canvas.height);

        if (remoteVideos.length > 0) {
            drawGrid(context, remoteVideos, canvas);
            if (localVideo) {
                drawLocalPreview(context, localVideo, canvas);
            }
            return;
        }

        if (localVideo) {
            drawVideoCover(context, localVideo, 0, 0, canvas.width, canvas.height);
            return;
        }

        drawEmptyFrame(context, canvas);
    }

    getRecordableVideos() {
        return Array.from(this.document.querySelectorAll('#participantGrid video, #remoteVideo, #localVideo'))
            .filter(video => video.srcObject && isElementVisible(video) && video.readyState >= 2);
    }

    download(blob, filename = this.buildFilename()) {
        const url = this.window.URL.createObjectURL(blob);
        const link = this.document.createElement('a');
        link.href = url;
        link.download = filename;
        link.style.display = 'none';
        this.document.body.appendChild(link);
        link.click();
        link.remove();

        this.window.setTimeout(() => {
            this.window.URL.revokeObjectURL(url);
        }, 1000);

        return filename;
    }

    buildFilename() {
        const datePart = new Date().toISOString().replace(/[:.]/g, '-');
        const meetingPart = this.connectionId ? `-${this.connectionId}` : '';
        return `meeting-recording${meetingPart}-${datePart}.${this.fileExtension || 'webm'}`;
    }

    cleanup() {
        if (this.animationFrameId) {
            this.window.cancelAnimationFrame(this.animationFrameId);
            this.animationFrameId = null;
        }

        if (this.recordingStream) {
            this.recordingStream.getTracks().forEach(track => track.stop());
            this.recordingStream = null;
        }

        if (this.audioContext) {
            this.audioContext.close();
            this.audioContext = null;
        }

        this.audioSources = [];
        this.mediaRecorder = null;
        this.canvas = null;
        this.context = null;
        this.chunks = [];
    }
}
