// Jasmine spec for voice-mesh.js's `tuneOpus` — the pure SDP-munging helper
// that turns on Opus in-band FEC + DTX and caps the bitrate for voice
// (Phase C of the my-sea invite/voice sprint). The RTCPeerConnection mesh
// itself is verified by hand; this pins the one deterministic, testable part.
describe('voice-mesh tuneOpus', function () {
    var SDP_WITH_FMTP =
        'v=0\r\n' +
        'm=audio 9 UDP/TLS/RTP/SAVPF 111\r\n' +
        'a=rtpmap:111 opus/48000/2\r\n' +
        'a=fmtp:111 minptime=10\r\n';

    var SDP_NO_FMTP =
        'v=0\r\n' +
        'm=audio 9 UDP/TLS/RTP/SAVPF 111\r\n' +
        'a=rtpmap:111 opus/48000/2\r\n';

    it('exposes tuneOpus globally', function () {
        expect(typeof window.tuneOpus).toBe('function');
    });

    it('appends FEC/DTX/bitrate to an existing opus fmtp line', function () {
        var out = window.tuneOpus(SDP_WITH_FMTP);
        expect(out).toContain('a=fmtp:111 minptime=10;');
        expect(out).toContain('useinbandfec=1');
        expect(out).toContain('usedtx=1');
        expect(out).toContain('maxaveragebitrate=40000');
    });

    it('adds an fmtp line when opus has none', function () {
        var out = window.tuneOpus(SDP_NO_FMTP);
        expect(out).toMatch(/a=fmtp:111 [^\r\n]*useinbandfec=1/);
    });

    it('leaves SDP without opus untouched', function () {
        var pcmu = 'v=0\r\nm=audio 9 RTP/AVP 0\r\na=rtpmap:0 PCMU/8000\r\n';
        expect(window.tuneOpus(pcmu)).toBe(pcmu);
    });

    it('is null-safe', function () {
        expect(window.tuneOpus('')).toBe('');
    });
});

// Mute path — the one deterministic slice of the mesh we can unit-test. The
// _applyMute extraction fixes Bug B (2026-05-29): a mute toggled while the
// desktop getUserMedia permission prompt is open must stick once the stream
// resolves (join re-applies it post-stream).
describe('voice-mesh mute', function () {
    var vr, trackA, trackB;

    beforeEach(function () {
        vr = window.VoiceRoom;
        trackA = { enabled: true };
        trackB = { enabled: true };
        vr.localStream = { getAudioTracks: function () { return [trackA, trackB]; } };
        vr.muted = false;
        vr._onState = null;
    });

    afterEach(function () {
        vr.localStream = null;
        vr.muted = false;
        vr._onState = null;
    });

    it('toggleMute disables the audio tracks + returns muted', function () {
        expect(vr.toggleMute()).toBe(true);
        expect(trackA.enabled).toBe(false);
        expect(trackB.enabled).toBe(false);
    });

    it('toggleMute twice re-enables the tracks', function () {
        vr.toggleMute();
        expect(vr.toggleMute()).toBe(false);
        expect(trackA.enabled).toBe(true);
        expect(trackB.enabled).toBe(true);
    });

    it('_applyMute honors a mute set before the stream existed (Bug B)', function () {
        // muted toggled while the permission prompt was open (no stream yet)…
        vr.localStream = null;
        vr.muted = true;
        // …then the stream arrives and join calls _applyMute.
        vr.localStream = { getAudioTracks: function () { return [trackA]; } };
        vr._applyMute();
        expect(trackA.enabled).toBe(false);
    });

    it('notifies subscribers with the muted flag on toggle', function () {
        var seen = null;
        vr.setOnStateChange(function (st) { seen = st; });
        vr.toggleMute();
        expect(seen.muted).toBe(true);
    });
});

// Secure-context join guard — getUserMedia is undefined over plain HTTP on a
// LAN IP (dev server reached from a phone), so join must fail fast + loud
// rather than fetch TURN creds and silently reject with no mic prompt
// (diagnosed 2026-05-29).
describe('voice-mesh join guard', function () {
    var vr;
    beforeEach(function () { vr = window.VoiceRoom; });
    afterEach(function () {
        vr.localStream = null; vr.muted = false; vr._onState = null;
    });

    it('rejects with INSECURE_CONTEXT when the mic is unsupported', function (done) {
        spyOn(vr, '_micSupported').and.returnValue(false);
        vr.join('mysea-test').then(function () {
            done.fail('join should reject without a secure-context mic');
        }, function (e) {
            expect(e.code).toBe('INSECURE_CONTEXT');
            done();
        });
    });
});

// Equalizer input level — the deterministic edge (no analysers → 0). The
// volume-reactive readout itself needs a live mesh + real audio to verify.
describe('voice-mesh inputLevel', function () {
    it('is 0 when no peer analysers are attached', function () {
        var vr = window.VoiceRoom;
        var saved = vr._analysers;
        vr._analysers = {};
        expect(vr.inputLevel()).toBe(0);
        vr._analysers = saved;
    });
});

// bindVoiceBtn (burger-btn.js) must surface a failed join + roll back the
// optimistic in-call state so the next click can retry.
describe('voice btn join failure surfacing', function () {
    var vbtn, origVR, origBrief, banner;

    beforeEach(function () {
        vbtn = document.createElement('button');
        vbtn.id = 'id_voice_btn';
        vbtn.classList.add('active');
        vbtn.setAttribute('data-room-id', 'mysea-x');
        document.body.appendChild(vbtn);
        origVR = window.VoiceRoom;
        origBrief = window.Brief;
        window.VoiceRoom = {
            join: function () { return Promise.reject({ code: 'INSECURE_CONTEXT' }); },
        };
        banner = jasmine.createSpy('showBanner');
        window.Brief = { showBanner: banner };
        bindVoiceBtn();
    });

    afterEach(function () {
        window.VoiceRoom = origVR;
        window.Brief = origBrief;
        vbtn.remove();
    });

    it('rolls back .in-call + surfaces a Brief when join rejects', function (done) {
        vbtn.click();
        expect(vbtn.classList.contains('in-call')).toBe(true);   // optimistic
        setTimeout(function () {
            expect(vbtn.classList.contains('in-call')).toBe(false);
            expect(vbtn.dataset.inCall).toBeUndefined();
            expect(banner).toHaveBeenCalled();
            expect(banner.calls.mostRecent().args[0].line_text)
                .toContain('HTTPS');
            done();
        }, 0);
    });
});
