(function () {
    const cfg = window.USHS_CALL;
    if (!cfg) return;

    const startBtn = document.getElementById('startBtn');
    const muteBtn = document.getElementById('muteBtn');
    const endBtn = document.getElementById('endBtn');
    const statusEl = document.getElementById('callStatus');
    const remoteAudio = document.getElementById('remoteAudio');
    const localAudio = document.getElementById('localAudio');
    const notesForm = document.getElementById('notesForm');
    const notesInput = document.getElementById('callNotes');
    const recordingStatus = document.getElementById('recordingStatus');

    let socket = null;
    let pc = null;
    let localStream = null;
    let remoteStream = null;
    let startedAt = null;
    let isMuted = false;
    let hasJoined = false;
    let remoteDescriptionSet = false;
    let pendingIceCandidates = [];
    let startNotified = false;

    // Recording state. The recorder is intentionally started without a timeslice.
    // Some browsers show only the first chunk duration when many WebM chunks are concatenated.
    // Recording one continuous blob gives the Replay control the correct full call length.
    let recordAudioContext = null;
    let recordDestination = null;
    let recordSources = [];
    let mediaRecorder = null;
    let recordedChunks = [];
    let isRecording = false;
    let recordingUploaded = false;
    let recordingStartTimer = null;
    let recordingStartedAt = null;
    let remoteStreamRecorded = false;

    const iceServers = {
        iceServers: [
            { urls: 'stun:stun.l.google.com:19302' }
            // Production recommendation: add TURN server credentials here for strict networks.
        ]
    };

    function setStatus(text) {
        if (statusEl) statusEl.textContent = text;
    }

    function setRecordingStatus(text, active) {
        if (!recordingStatus) return;
        recordingStatus.classList.toggle('idle', !active);
        recordingStatus.innerHTML = active
            ? '<span class="recording-dot"></span>' + text
            : '<i class="fas fa-circle-info"></i> ' + text;
    }

    function isCsrCall() {
        return cfg.isCsr === true || cfg.participantType === 'csr';
    }

    function hasLiveAudioTrack(stream) {
        return !!(stream && stream.getAudioTracks && stream.getAudioTracks().some(track => track.readyState === 'live'));
    }

    async function resumeAudioContext() {
        if (recordAudioContext && recordAudioContext.state === 'suspended') {
            try { await recordAudioContext.resume(); } catch (err) {}
        }
    }

    function addStreamToRecording(stream, label) {
        if (!recordDestination || !recordAudioContext || !stream || !hasLiveAudioTrack(stream)) return false;

        try {
            const source = recordAudioContext.createMediaStreamSource(stream);
            source.connect(recordDestination);
            recordSources.push({ source, label: label || 'audio' });
            if (label === 'remote') remoteStreamRecorded = true;
            return true;
        } catch (err) {
            console.warn('Unable to add ' + (label || 'audio') + ' stream to recording mix:', err);
            return false;
        }
    }

    async function startAutoRecording(reason) {
        if (!isCsrCall() || isRecording || recordingUploaded || !window.MediaRecorder || !localStream) return;

        try {
            const AudioContextClass = window.AudioContext || window.webkitAudioContext;
            if (!AudioContextClass) throw new Error('AudioContext is not supported.');

            recordAudioContext = new AudioContextClass();
            await resumeAudioContext();

            recordDestination = recordAudioContext.createMediaStreamDestination();
            recordSources = [];
            recordedChunks = [];
            remoteStreamRecorded = false;

            addStreamToRecording(localStream, 'local');
            if (remoteStream) addStreamToRecording(remoteStream, 'remote');

            let mimeType = '';
            if (MediaRecorder.isTypeSupported && MediaRecorder.isTypeSupported('audio/webm;codecs=opus')) {
                mimeType = 'audio/webm;codecs=opus';
            } else if (MediaRecorder.isTypeSupported && MediaRecorder.isTypeSupported('audio/webm')) {
                mimeType = 'audio/webm';
            }

            mediaRecorder = mimeType
                ? new MediaRecorder(recordDestination.stream, { mimeType })
                : new MediaRecorder(recordDestination.stream);

            mediaRecorder.ondataavailable = function (event) {
                if (event.data && event.data.size > 0) recordedChunks.push(event.data);
            };

            mediaRecorder.onerror = function (event) {
                console.warn('MediaRecorder error:', event);
                setRecordingStatus('Recording issue detected. Please keep the call room open.', false);
            };

            mediaRecorder.onstart = function () {
                recordingStartedAt = Date.now();
                isRecording = true;
                setRecordingStatus(remoteStreamRecorded ? 'Recording call automatically...' : 'Recording CSR audio. Waiting for customer audio...', true);
            };

            mediaRecorder.start();
        } catch (err) {
            console.warn('Automatic recording failed:', err);
            setRecordingStatus('Automatic recording unavailable in this browser.', false);
        }
    }

    function maybeStartAutoRecording(reason) {
        if (!isCsrCall() || isRecording || recordingUploaded || !localStream) return;

        if (hasLiveAudioTrack(remoteStream)) {
            if (recordingStartTimer) {
                clearTimeout(recordingStartTimer);
                recordingStartTimer = null;
            }
            startAutoRecording(reason || 'remote-audio');
            return;
        }

        setRecordingStatus('Waiting for customer audio before recording...', false);

        // Fallback: if the customer audio track is delayed, still record CSR audio so there is a record.
        if (!recordingStartTimer) {
            recordingStartTimer = setTimeout(function () {
                recordingStartTimer = null;
                startAutoRecording('fallback-local-only');
            }, 12000);
        }
    }

    async function stopAndUploadRecording() {
        if (!isCsrCall() || recordingUploaded) return;

        if (recordingStartTimer) {
            clearTimeout(recordingStartTimer);
            recordingStartTimer = null;
        }

        if (!mediaRecorder || !isRecording) return;

        await new Promise(function (resolve) {
            try {
                mediaRecorder.onstop = resolve;
                if (mediaRecorder.state !== 'inactive') {
                    mediaRecorder.stop();
                } else {
                    resolve();
                }
            } catch (err) {
                resolve();
            }
        });

        isRecording = false;
        setRecordingStatus('Saving call recording...', false);

        if (recordAudioContext) {
            try { await recordAudioContext.close(); } catch (err) {}
        }

        recordAudioContext = null;
        recordDestination = null;
        recordSources = [];

        if (!recordedChunks.length) {
            setRecordingStatus('No recording captured.', false);
            return;
        }

        const blob = new Blob(recordedChunks, { type: recordedChunks[0].type || 'audio/webm' });
        const form = new FormData();
        form.append('room_token', cfg.roomToken);
        form.append('call_id', String(cfg.callId || ''));
        form.append('recording_duration_seconds', String(recordingStartedAt ? Math.max(0, Math.round((Date.now() - recordingStartedAt) / 1000)) : 0));
        form.append('recording', blob, 'call_' + (cfg.callId || 'webrtc') + '_' + Date.now() + '.webm');

        try {
            const res = await fetch(cfg.apiBase + '/upload_recording.php', {
                method: 'POST',
                body: form,
                credentials: 'same-origin'
            });
            const data = await res.json().catch(() => ({}));
            recordingUploaded = !!data.ok;
            setRecordingStatus(data.ok ? 'Recording saved.' : (data.error || 'Recording upload failed.'), false);
        } catch (err) {
            setRecordingStatus('Recording upload failed.', false);
        }
    }

    async function notifyCallStarted() {
        if (!isCsrCall() || startNotified) return;
        startNotified = true;

        const form = new FormData();
        form.append('room_token', cfg.roomToken);
        form.append('call_id', String(cfg.callId || ''));

        try {
            await fetch(cfg.apiBase + '/start_call.php', {
                method: 'POST',
                body: form,
                credentials: 'same-origin'
            });
        } catch (err) {}
    }

    function durationSeconds() {
        if (!startedAt) return 0;
        return Math.floor((Date.now() - startedAt) / 1000);
    }

    function setButtons(joined) {
        if (startBtn) startBtn.disabled = joined;
        if (muteBtn) muteBtn.disabled = !joined;
        if (endBtn) endBtn.disabled = !joined;
    }

    async function flushPendingIceCandidates() {
        if (!pc || !remoteDescriptionSet || pendingIceCandidates.length === 0) return;

        const candidates = pendingIceCandidates.slice();
        pendingIceCandidates = [];

        for (const candidate of candidates) {
            try {
                await pc.addIceCandidate(new RTCIceCandidate(candidate));
            } catch (err) {
                console.error('Queued ICE candidate error:', err);
            }
        }
    }

    async function createPeerConnection() {
        pc = new RTCPeerConnection(iceServers);

        pc.onicecandidate = (event) => {
            if (event.candidate && socket) {
                socket.emit('ice-candidate', {
                    roomToken: cfg.roomToken,
                    candidate: event.candidate
                });
            }
        };

        pc.ontrack = (event) => {
            if (event.streams && event.streams[0]) {
                remoteStream = event.streams[0];
                if (remoteAudio) {
                    remoteAudio.srcObject = remoteStream;
                    remoteAudio.play().catch(function () {});
                }

                if (isRecording && !remoteStreamRecorded) {
                    const added = addStreamToRecording(remoteStream, 'remote');
                    if (added) setRecordingStatus('Recording call automatically...', true);
                } else {
                    maybeStartAutoRecording('remote-track');
                }
            }
        };

        pc.onconnectionstatechange = () => {
            setStatus('Call connection: ' + pc.connectionState);
            if (pc.connectionState === 'connected') {
                startedAt = startedAt || Date.now();
                maybeStartAutoRecording('peer-connected');
            }
            if (['failed', 'disconnected'].includes(pc.connectionState)) {
                setStatus('Call connection issue: ' + pc.connectionState + '. You may need to rejoin.');
            }
        };

        pc.oniceconnectionstatechange = () => {
            if (pc && ['connected', 'completed'].includes(pc.iceConnectionState)) {
                startedAt = startedAt || Date.now();
                maybeStartAutoRecording('ice-connected');
            }
        };

        localStream.getTracks().forEach((track) => {
            pc.addTrack(track, localStream);
        });
    }

    async function joinCall() {
        if (hasJoined) return;
        hasJoined = true;
        remoteDescriptionSet = false;
        pendingIceCandidates = [];

        try {
            if (typeof io === 'undefined') {
                throw new Error('Socket.IO client was not loaded. Check your internet connection or CDN access.');
            }

            setStatus('Requesting microphone permission...');
            console.log('USHS WebRTC config:', { signalingUrl: cfg.signalingUrl, signalingPath: cfg.signalingPath, roomToken: cfg.roomToken, participantType: cfg.participantType });

            localStream = await navigator.mediaDevices.getUserMedia({
                audio: true,
                video: false
            });

            if (localAudio) {
                localAudio.srcObject = localStream;
                localAudio.play().catch(function () {});
            }

            await notifyCallStarted();

            socket = io(cfg.signalingUrl, {
                path: cfg.signalingPath || '/socket.io',
                transports: ['polling', 'websocket'],
                forceNew: true,
                reconnectionAttempts: 8,
                reconnectionDelay: 1000,
                reconnectionDelayMax: 5000,
                timeout: 20000,
                upgrade: true,
                rememberUpgrade: false
            });

            await createPeerConnection();

            socket.on('connect', () => {
                setStatus('Connected to signaling server. Joining call room...');
                socket.emit('join-room', {
                    roomToken: cfg.roomToken,
                    userId: cfg.userId,
                    participantType: cfg.participantType
                });
            });

            socket.on('connect_error', (err) => {
                console.error('Socket connection error:', err);
                const reason = err && err.message ? ' (' + err.message + ')' : '';
                setStatus('Cannot connect to WebRTC server' + reason + '. Please restart the cPanel Node.js WebRTC app, then refresh this page.');
            });

            socket.on('server-error', (payload) => {
                setStatus((payload && payload.message) ? payload.message : 'WebRTC server error.');
            });

            socket.on('participant-update', (payload) => {
                if (!payload) return;
                if (payload.hasCustomer && payload.hasCsr) {
                    setStatus('Both participants are in the call room. Connecting audio...');
                    maybeStartAutoRecording('both-present');
                } else {
                    setStatus('Waiting for the other participant to join...');
                }
            });

            socket.on('ready-for-offer', async () => {
                if (cfg.participantType !== 'csr' || !pc) return;
                setStatus('Creating call offer...');
                const offer = await pc.createOffer({ offerToReceiveAudio: true, offerToReceiveVideo: false });
                await pc.setLocalDescription(offer);
                socket.emit('offer', { roomToken: cfg.roomToken, offer });
            });

            socket.on('offer', async (offer) => {
                if (!pc) return;
                setStatus('Received call offer. Creating answer...');
                await pc.setRemoteDescription(new RTCSessionDescription(offer));
                remoteDescriptionSet = true;
                await flushPendingIceCandidates();

                const answer = await pc.createAnswer();
                await pc.setLocalDescription(answer);
                socket.emit('answer', { roomToken: cfg.roomToken, answer });
            });

            socket.on('answer', async (answer) => {
                if (!pc) return;
                setStatus('Received answer. Connecting audio...');
                await pc.setRemoteDescription(new RTCSessionDescription(answer));
                remoteDescriptionSet = true;
                await flushPendingIceCandidates();
            });

            socket.on('ice-candidate', async (candidate) => {
                if (!candidate || !pc) return;
                if (!remoteDescriptionSet) {
                    pendingIceCandidates.push(candidate);
                    return;
                }

                try {
                    await pc.addIceCandidate(new RTCIceCandidate(candidate));
                } catch (err) {
                    console.error('ICE candidate error:', err);
                }
            });

            socket.on('call-ended', async () => {
                setStatus('The other participant ended the call. Saving recording...');
                await stopAndUploadRecording();
                await cleanup(false);
                setStatus('The other participant ended the call.');
            });

            socket.on('participant-left', (payload) => {
                const label = payload && payload.participantType ? payload.participantType : 'participant';
                setStatus('The other ' + label + ' left the call room.');
            });

            setButtons(true);
            setStatus('Waiting for the other participant...');
            maybeStartAutoRecording('join-fallback');
        } catch (err) {
            console.error(err);
            setStatus('Unable to start call: ' + err.message);
            hasJoined = false;
            setButtons(false);
        }
    }

    async function cleanup(notifyServer) {
        if (notifyServer && socket) {
            socket.emit('end-call', { roomToken: cfg.roomToken });
        }

        if (recordingStartTimer) {
            clearTimeout(recordingStartTimer);
            recordingStartTimer = null;
        }

        if (localStream) localStream.getTracks().forEach(track => track.stop());
        if (pc) pc.close();
        if (socket) socket.disconnect();

        if (remoteAudio) remoteAudio.srcObject = null;
        if (localAudio) localAudio.srcObject = null;

        localStream = null;
        remoteStream = null;
        remoteStreamRecorded = false;
        pc = null;
        socket = null;
        hasJoined = false;
        remoteDescriptionSet = false;
        pendingIceCandidates = [];
        setButtons(false);
    }

    async function endCall() {
        const seconds = durationSeconds();
        setStatus('Saving call recording...');
        await stopAndUploadRecording();
        await cleanup(true);
        setStatus('Call ended. Saving call log...');

        const form = new FormData();
        form.append('room_token', cfg.roomToken);
        form.append('duration_seconds', seconds.toString());
        form.append('result', seconds > 0 ? 'connected' : 'ended');

        try {
            const res = await fetch(cfg.apiBase + '/end_call.php', {
                method: 'POST',
                body: form,
                credentials: 'same-origin'
            });
            const data = await res.json().catch(() => ({}));
            setStatus(data.ok === false ? (data.error || 'Call ended, but saving failed.') : 'Call ended and saved.');
        } catch (err) {
            setStatus('Call ended, but saving failed.');
        }
    }

    function toggleMute() {
        if (!localStream) return;
        isMuted = !isMuted;
        localStream.getAudioTracks().forEach(track => { track.enabled = !isMuted; });
        if (muteBtn) muteBtn.textContent = isMuted ? 'Unmute' : 'Mute';
    }

    async function saveNotes(event) {
        event.preventDefault();
        if (!notesInput) return;

        const form = new FormData();
        form.append('room_token', cfg.roomToken);
        form.append('notes', notesInput.value || '');

        try {
            const res = await fetch(cfg.apiBase + '/save_call_notes.php', {
                method: 'POST',
                body: form,
                credentials: 'same-origin'
            });
            const data = await res.json();
            alert(data.ok ? 'Notes saved.' : (data.error || 'Unable to save notes.'));
        } catch (err) {
            alert('Unable to save notes.');
        }
    }

    window.addEventListener('beforeunload', function (event) {
        if (isRecording) {
            event.preventDefault();
            event.returnValue = 'A call recording is still active. End the call first before leaving.';
            return event.returnValue;
        }
    });

    if (startBtn) startBtn.addEventListener('click', joinCall);
    if (muteBtn) muteBtn.addEventListener('click', toggleMute);
    if (endBtn) endBtn.addEventListener('click', endCall);
    if (notesForm) notesForm.addEventListener('submit', saveNotes);
})();
