// Burger btn + 5-fan menu on room.html. Pure scaffolding for now —
// sub-btns have no click handlers in this sprint. Behaviour owned here:
//   • Toggle #id_burger_btn.active on click.
//   • Closing burger via re-click / Escape / click-outside.
//   • Opening burger auto-closes the kit dialog (#id_kit_bag_dialog) and
//     the bud slide-out panel (html.bud-open) if either is open —
//     dispatched by clicking the owning btn, which routes through that
//     btn's own toggle/close path (no fetch on close).
//
// bindBurger() returns an AbortController so test code (and any future
// re-bind callers) can detach all listeners cleanly via ac.abort().

(function () {
    'use strict';

    function bindBurger() {
        var btn = document.getElementById('id_burger_btn');
        var fan = document.getElementById('id_burger_fan');
        if (!btn || !fan) return null;

        var ac = new AbortController();
        var sig = ac.signal;

        function _isOpen() {
            return btn.classList.contains('active');
        }

        function _closeKit() {
            var dialog = document.getElementById('id_kit_bag_dialog');
            var kitBtn = document.getElementById('id_kit_btn');
            if (dialog && dialog.hasAttribute('open') && kitBtn) {
                kitBtn.click();
            }
        }

        function _closeBud() {
            var budBtn = document.getElementById('id_bud_btn');
            if (document.documentElement.classList.contains('bud-open') && budBtn) {
                budBtn.click();
            }
        }

        function _open() {
            _closeKit();
            _closeBud();
            btn.classList.add('active');
            btn.setAttribute('aria-expanded', 'true');
            fan.setAttribute('aria-hidden', 'false');
        }

        function _close() {
            btn.classList.remove('active');
            btn.setAttribute('aria-expanded', 'false');
            fan.setAttribute('aria-hidden', 'true');
        }

        // 2 pulses, ~180ms ON / 120ms OFF — tighter cadence than
        // sig-select's countdown glow (600ms), but same shape.
        function _flashInactive(subBtn) {
            var pulses = 2;
            var onMs = 180;
            var offMs = 120;
            function pulse(remaining) {
                if (remaining <= 0) return;
                subBtn.classList.add('flash-inactive');
                setTimeout(function () {
                    subBtn.classList.remove('flash-inactive');
                    setTimeout(function () {
                        pulse(remaining - 1);
                    }, offMs);
                }, onMs);
            }
            pulse(pulses);
        }

        btn.addEventListener('click', function (e) {
            e.stopPropagation();
            if (_isOpen()) _close();
            else _open();
        }, { signal: sig });

        // Delegated click on the fan:
        //   • INACTIVE sub-btn → flash the --priRd glow twice (no real action
        //     bound yet for that surface).
        //   • ACTIVE sub-btn → close the burger fan so the surface's own
        //     handler (page-level direct listener on the sub-btn) takes over
        //     w. a clean visual. The per-page handler fires BEFORE this
        //     delegated one (target-phase before bubble-up), so the action
        //     already kicked off by the time we close.
        fan.addEventListener('click', function (e) {
            e.stopPropagation();
            var subBtn = e.target.closest('.burger-fan-btn');
            if (!subBtn) return;
            if (subBtn.classList.contains('active')) {
                _close();
            } else {
                _flashInactive(subBtn);
            }
        }, { signal: sig });

        document.addEventListener('keydown', function (e) {
            if (e.key === 'Escape' && _isOpen()) _close();
        }, { signal: sig });

        document.addEventListener('click', function (e) {
            if (!_isOpen()) return;
            if (btn.contains(e.target)) return;
            if (fan.contains(e.target)) return;
            _close();
        }, { signal: sig });

        return ac;
    }

    window.bindBurger = bindBurger;

    // Voice sub-btn (Phase C of the my-sea invite/voice sprint). Direct
    // listener on #id_voice_btn: an ACTIVE click lazy-loads voice-mesh.js
    // then joins the mesh (first click) or toggles mute (subsequent). An
    // INACTIVE click is left to the delegated fan handler's 2-pulse flash.
    // No stopPropagation on active — the delegated handler then closes the
    // fan (its existing .active behaviour).
    // Voice persistence across my_sea reloads (2026-05-29): we remember the
    // active room in sessionStorage so the next my_sea page silently re-joins
    // the mesh (mic permission persists for the session → no prompt). True
    // no-reload nav would need an SPA refactor of the draw IIFEs; this gets the
    // same user-visible result (a brief reconnect, not seamless) with no risk
    // to those flows. The flag is cleared only on an EXPLICIT leave (BYE /
    // NVM-disconnect guard) or a failed join, never on an in-my_sea reload.
    var VOICE_ROOM_KEY = 'mysea-voice-room';
    function _rememberVoiceRoom(id) {
        try { sessionStorage.setItem(VOICE_ROOM_KEY, id); } catch (e) {}
    }
    function _forgetVoiceRoom() {
        try { sessionStorage.removeItem(VOICE_ROOM_KEY); } catch (e) {}
    }
    function _rememberedVoiceRoom() {
        try { return sessionStorage.getItem(VOICE_ROOM_KEY); } catch (e) { return null; }
    }
    // BYE + the NVM-disconnect guard call this on an explicit leave.
    window.mySeaVoiceForget = _forgetVoiceRoom;

    // ── Mute persistence + 3-min auto-disconnect (user-spec 2026-05-30) ─────
    // The mute is stored server-side (User.voice_muted_at) so it SURVIVES in-sea
    // nav/refresh — the voice auto-rejoin re-applies it. A mute held for
    // MUTE_MAX_MS auto-disconnects the user from voice (+ clears the field). The
    // window anchors to the persisted timestamp, so it spans navigations instead
    // of resetting on each page.
    var MUTE_MAX_MS = 180000;   // 3 minutes
    var _muteTimer = null;

    function _voiceCsrf() {
        var m = document.cookie.match(/(?:^|; )csrftoken=([^;]+)/);
        return m ? decodeURIComponent(m[1]) : '';
    }
    // Persist (or clear) the caller's mute server-side. Best-effort — voice
    // still works locally if the POST fails; only cross-nav persistence is lost.
    function _persistMute(muted) {
        try {
            fetch('/voice/mute', {
                method: 'POST',
                credentials: 'same-origin',
                headers: { 'Content-Type': 'application/json', 'X-CSRFToken': _voiceCsrf() },
                body: JSON.stringify({ muted: !!muted }),
            }).catch(function () {});
        } catch (e) {}
    }
    // ms remaining before the 3-min auto-disconnect, given the mute's start (ms)
    // + now (ms). <= 0 means already elapsed. Pure — exposed for unit testing.
    function muteRemainingMs(mutedAtMs, nowMs) {
        return MUTE_MAX_MS - (nowMs - mutedAtMs);
    }
    window._muteRemainingMs = muteRemainingMs;

    function _clearMuteTimer() {
        if (_muteTimer) { clearTimeout(_muteTimer); _muteTimer = null; }
    }
    // Arm the auto-disconnect for the mute that began at `mutedAtMs` (ms). Fires
    // `onFire` after the remaining window (next tick if already elapsed).
    function _armMuteTimer(mutedAtMs, onFire) {
        _clearMuteTimer();
        var remaining = muteRemainingMs(mutedAtMs, Date.now());
        _muteTimer = setTimeout(onFire, remaining > 0 ? remaining : 0);
    }

    // Surface a join failure to the user instead of failing silently — most
    // often the secure-context block (INSECURE_CONTEXT) when the dev server is
    // reached over plain HTTP from a phone. Prefers the Brief banner; falls
    // back to console.
    function _voiceJoinFailed(vbtn, e) {
        vbtn.classList.remove('in-call');
        delete vbtn.dataset.inCall;   // let the next click retry the join
        _forgetVoiceRoom();           // don't auto-rejoin a context that can't talk
        var msg = (e && e.code === 'INSECURE_CONTEXT')
            ? 'Voice needs HTTPS (or localhost) — your browser blocked the mic here.'
            : 'Couldn’t start voice — mic unavailable or permission denied.';
        if (window.Brief && typeof window.Brief.showBanner === 'function') {
            window.Brief.showBanner({
                title: 'Voice', line_text: msg, kind: 'NUDGE',
                post_url: '', created_at: '',
            });
        } else if (window.console && console.warn) {
            console.warn('[voice] ' + msg, e || '');
        }
    }

    function bindVoiceBtn() {
        var vbtn = document.getElementById('id_voice_btn');
        if (!vbtn) return;
        var roomId = vbtn.getAttribute('data-room-id');
        // Persisted mute timestamp (server-rendered) — present iff the user was
        // muted when this page loaded. Drives the auto-rejoin's mute + window.
        var mutedAtAttr = vbtn.getAttribute('data-voice-muted-at');
        var persistedMutedAtMs = mutedAtAttr ? Date.parse(mutedAtAttr) : NaN;
        var hasPersistedMute = !isNaN(persistedMutedAtMs);

        // VoiceRoom is lazy-loaded on first use (mesh injected on demand).
        function withVoiceRoom(cb) {
            if (window.VoiceRoom) { cb(); return; }
            var s = document.createElement('script');
            s.src = '/static/apps/voice/voice-mesh.js';
            s.onload = cb;
            document.head.appendChild(s);
        }

        // 3-min muted timeout fired: leave voice, forget the room (no rejoin),
        // clear the muted UI + the server field.
        function _muteAutoDisconnect() {
            _clearMuteTimer();
            if (window.VoiceRoom && window.VoiceRoom.leave) window.VoiceRoom.leave();
            _forgetVoiceRoom();
            _persistMute(false);
            vbtn.classList.remove('muted', 'in-call');
            delete vbtn.dataset.inCall;
        }

        function startCall() {
            if (!window.VoiceRoom || vbtn.dataset.inCall) return;
            vbtn.dataset.inCall = '1';
            vbtn.classList.add('in-call');
            _rememberVoiceRoom(roomId);
            var p = window.VoiceRoom.join(roomId);
            if (p && typeof p.catch === 'function') {
                p.catch(function (e) { _voiceJoinFailed(vbtn, e); });
            }
        }

        vbtn.addEventListener('click', function () {
            if (!vbtn.classList.contains('active')) return;  // → delegated flash
            if (!roomId) return;
            withVoiceRoom(function () {
                if (!window.VoiceRoom) return;
                if (!vbtn.dataset.inCall) {
                    // Fresh MANUAL join → start unmuted; clear any stale persisted
                    // mute (e.g. muted, then closed the tab, last session).
                    _clearMuteTimer();
                    _persistMute(false);
                    startCall();
                } else {
                    var muted = window.VoiceRoom.toggleMute();
                    vbtn.classList.toggle('muted', muted);
                    _persistMute(muted);
                    if (muted) _armMuteTimer(Date.now(), _muteAutoDisconnect);
                    else _clearMuteTimer();
                }
            });
        });

        // Auto-rejoin: were we in THIS room before the navigation, and is voice
        // still available on this page? Silently re-join so the call survives an
        // in-sea reload (GATE VIEW / NVM / draw nav) — CARRYING the mute state.
        if (roomId && vbtn.classList.contains('active')
            && _rememberedVoiceRoom() === roomId) {
            if (hasPersistedMute && muteRemainingMs(persistedMutedAtMs, Date.now()) <= 0) {
                // The 3-min mute window already elapsed (muted across a long
                // idle / nav) → honour the auto-disconnect: don't rejoin, clear.
                _forgetVoiceRoom();
                _persistMute(false);
                vbtn.classList.remove('muted');
            } else {
                withVoiceRoom(function () {
                    if (hasPersistedMute && window.VoiceRoom.setMuted) {
                        window.VoiceRoom.setMuted(true);   // honoured post-getUserMedia
                        vbtn.classList.add('muted');
                        _armMuteTimer(persistedMutedAtMs, _muteAutoDisconnect);
                    }
                    startCall();
                });
            }
        }
    }
    window.bindVoiceBtn = bindVoiceBtn;

    if (document.readyState === 'loading') {
        document.addEventListener('DOMContentLoaded', function () {
            bindBurger();
            bindVoiceBtn();
        });
    } else {
        bindBurger();
        bindVoiceBtn();
    }
}());
