{% extends "core/base.html" %}
{% load static %}
{% load lyric_extras %}

{% block title_text %}Game Sea{% endblock title_text %}
{% block header_text %}<span>Game</span><span>Sea</span>{% endblock header_text %}

{% block content %}
{# Phase B spectator surface — an ACCEPTED invitee watches {{ owner|at_handle }}'s #}
{# Sea read-only. The owner's my_sea.html is left untouched; this is a       #}
{# dedicated, simpler template: the table hex (seat 1C = owner, 2C = this     #}
{# visitor) + the owner's draw rendered read-only (no FLIP / DEL / AUTO       #}
{# DRAW). GATE VIEW opens the visitor gate; once a token is deposited the     #}
{# center btn becomes VIEW DRAW (toggles the read-only draw into view).       #}
<div class="my-sea-page my-sea-visit-page"
     data-phase="landing"
     data-spectator="true"
     data-polarity="{% if significator_reversed %}levity{% else %}gravity{% endif %}">

    <div class="my-sea-landing my-sea-visit-landing">
        <div class="room-shell">
            <div id="id_game_table" class="room-table">
                <div class="room-table-scene">
                    <div class="table-hex-border">
                        <div class="table-hex">
                            <div class="table-center">
                                {% if seat2_present %}
                                    {# Visitor is present — VIEW DRAW reveals the owner's #}
                                    {# read-only draw (client-side toggle below).         #}
                                    <button id="id_my_sea_view_draw_btn" type="button"
                                            class="btn btn-primary">VIEW<br>DRAW</button>
                                {% else %}
                                    {# Not yet present — GATE VIEW → visitor token gate.  #}
                                    <button id="id_my_sea_gate_view_btn" type="button"
                                            class="btn btn-primary"
                                            data-gate-url="{% url 'my_sea_visit_gate' owner.id %}">GATE<br>VIEW</button>
                                {% endif %}
                            </div>
                        </div>
                    </div>
                    {# Every present member shows on the ring — owner 1C +    #}
                    {# present invitees 2C–6C by deposit order (the viewer's  #}
                    {# own seat carries `--self`). Built server-side as       #}
                    {# `seats` so all viewers see identical absolute seating. #}
                    {% for seat in seats %}
                        {% if seat.present %}
                            <div class="table-seat seated{% if seat.is_self %} table-seat--self{% endif %}" data-slot="{{ seat.n }}" data-seat-token="{{ seat.token }}">
                                <i class="fa-solid fa-chair"></i>
                                <span class="seat-position-label">{{ seat.label }}</span>
                                <i class="position-status-icon fa-solid fa-circle-check"></i>
                            </div>
                        {% else %}
                            <div class="table-seat" data-slot="{{ seat.n }}">
                                <i class="fa-solid fa-chair"></i>
                                <span class="seat-position-label">{{ seat.label }}</span>
                                <i class="position-status-icon fa-solid fa-ban"></i>
                            </div>
                        {% endif %}
                    {% endfor %}
                </div>
            </div>
        </div>
    </div>

    {% if seat2_present %}
    {# Owner's draw, read-only. Hidden until VIEW DRAW toggles it in. Renders #}
    {# the SAME interactive cross stage the owner sees (click→stage, hover,    #}
    {# SPIN, FYI) off the owner's draw payload — see _my_sea_visit_cross.html. #}
    <div id="id_my_sea_visit_draw" class="my-sea-visit-draw" style="display:none">
        {{ sea_deck_data|json_script:"id_my_sea_deck" }}
        {{ position_labels|json_script:"id_my_sea_position_labels" }}
        {% include "apps/gameboard/_partials/_my_sea_visit_cross.html" %}
    </div>
    {% endif %}

    {# Gear menu — NVM is phase-aware (mySeaVisitNvm): table hex → the bud's    #}
    {# page (voice-disconnect guard); draw phase → back to the hex (client      #}
    {# toggle, stays in-ecosphere, no guard). BYE drops presence + frees 2C.    #}
    {% url 'my_sea_visit_leave' owner.id as leave_url %}
    {% include "apps/gameboard/_partials/_my_sea_gear.html" with leave_url=leave_url nvm_handler="mySeaVisitNvm(event)" %}
    {# Burger fan — carries the voice sub-btn (active while voice_active). #}
    {% include "apps/gameboard/_partials/_burger.html" %}
</div>
{% endblock content %}

{% block scripts %}
    {# room.js's scaleTable() fits the table-hex scene to the aperture —     #}
    {# same as the owner's my_sea (which loads it too); without it the visit #}
    {# hex renders unscaled.                                                 #}
    <script src="{% static 'apps/epic/room.js' %}"></script>
    <script src="{% static 'apps/gameboard/my-sea-seats.js' %}"></script>
    <script src="{% static 'apps/epic/burger-btn.js' %}"></script>
    {# Voice-affordance glow/pulse machine — keys on the voice sub-btn's      #}
    {# availability + the live mesh state (VoiceRoom.setOnStateChange).        #}
    <script src="{% static 'apps/voice/voice-glow.js' %}"></script>
    {% if seat2_present %}
    {# Read-only cross stage — StageCard + SeaDeal bind to #id_sea_overlay   #}
    {# (inside #id_my_sea_visit_draw) for click→stage + SPIN + FYI + hover.  #}
    {# A trimmed seed IIFE (no picker/FLIP/DEL machinery) reconstructs       #}
    {# SeaDeal's `_seaHand` from the server-rendered filled slots so each    #}
    {# card is clickable into the magnified stage — same logic as my_sea's   #}
    {# saved-hand restore, just without the draw affordances.                #}
    <script src="{% static 'apps/epic/stage-card.js' %}"></script>
    <script src="{% static 'apps/epic/sea.js' %}"></script>
    <script>
    (function () {
        function _seed() {
            if (!window.SeaDeal || !window.SeaDeal.seedHand) return;
            var cross = document.querySelector('.my-sea-cross');
            if (!cross) return;
            var deckEl = document.getElementById('id_my_sea_deck');
            var deck = {};
            try { deck = JSON.parse((deckEl && deckEl.textContent) || '{}'); } catch (e) {}
            var byId = {};
            (deck.levity || []).concat(deck.gravity || []).forEach(function (c) {
                byId[c.id] = c;
            });
            var seed = {};
            cross.querySelectorAll('.sea-card-slot.sea-card-slot--filled').forEach(function (slot) {
                var posName = slot.dataset.posKey;
                var card = byId[parseInt(slot.dataset.cardId, 10)];
                if (!posName || !card) return;
                var slotCard = {};
                for (var k in card) {
                    if (Object.prototype.hasOwnProperty.call(card, k)) slotCard[k] = card[k];
                }
                // Per-instance reversed/polarity come from the slot's DOM
                // class (the owner's saved hand, not the deck-shuffle axis).
                slotCard.reversed = slot.classList.contains('sea-card-slot--reversed');
                var isLevity = slot.classList.contains('sea-card-slot--levity');
                seed[posName] = { card: slotCard, isLevity: isLevity };
            });
            window.SeaDeal.seedHand(seed);
        }
        if (document.readyState === 'loading') {
            document.addEventListener('DOMContentLoaded', _seed);
        } else {
            _seed();
        }
    }());
    </script>
    {# Deck-stack persist — the spectator's FLIP is disabled (read-only), but    #}
    {# clicking a stack PERSISTS its glow + the revealed × via the shared        #}
    {# `.sea-deck-stack--active` class (same persist the owner picker uses,      #}
    {# user-spec 2026-05-30). Hover-reveal + the fade are the shared CSS; this   #}
    {# only adds the click-to-lock. Clicking another stack / elsewhere clears.   #}
    <script>
    (function () {
        var overlay = document.getElementById('id_sea_overlay');
        if (!overlay) return;
        var stacks = overlay.querySelectorAll('.sea-deck-stack');
        if (!stacks.length) return;
        function clearAll(except) {
            stacks.forEach(function (s) {
                if (s !== except) s.classList.remove('sea-deck-stack--active');
            });
        }
        stacks.forEach(function (stack) {
            stack.addEventListener('click', function (e) {
                e.stopPropagation();   // don't let the overlay handler clear it
                var on = stack.classList.contains('sea-deck-stack--active');
                clearAll(stack);
                stack.classList.toggle('sea-deck-stack--active', !on);
            });
        });
        // A click anywhere else on the cross drops the persisted reveal.
        overlay.addEventListener('click', function () { clearAll(null); });
    }());
    </script>
    {# Async witness — a WS to `mysea_<owner>` pushes the owner's hand as each #}
    {# card lands; SeaDeal.register fills the cross slot (+ seeds it clickable) #}
    {# live, no refresh. A DEL (empty hand) re-empties the cleared slots.       #}
    <script>
    (function () {
        if (!('WebSocket' in window)) return;
        var deckEl = document.getElementById('id_my_sea_deck');
        var deck = {};
        try { deck = JSON.parse((deckEl && deckEl.textContent) || '{}'); } catch (e) {}
        var byId = {};
        (deck.levity || []).concat(deck.gravity || []).forEach(function (c) { byId[c.id] = c; });
        // Per-spread caption map (server-sourced from POSITION_LABELS) — used to
        // re-caption the cross live when the owner switches spreads.
        var POSITION_LABELS = {};
        var plEl = document.getElementById('id_my_sea_position_labels');
        try { POSITION_LABELS = JSON.parse((plEl && plEl.textContent) || '{}'); } catch (e) {}
        // This viewer's own seat token — so a live seat re-render can re-mark
        // their --self chair without the server knowing who's watching.
        var MY_SEAT_TOKEN = 'visit-{{ sea_invite.id }}';

        // Re-sync the cross to the owner's CURRENT spread (carried on each
        // `sea_draw`). A post-DEL spread switch changes both which cells are
        // active (CSS keys off `[data-spread]`) AND their captions — without
        // this, the owner's new cards land into the OLD spread's cells, the
        // asymmetry the user reported 2026-05-30. Clears any stale fill so the
        // subsequent _applyHand repopulates against the right layout.
        function _applySpread(spread) {
            var cross = document.querySelector('.my-sea-cross');
            if (!cross || !spread || cross.getAttribute('data-spread') === spread) return;
            cross.setAttribute('data-spread', spread);
            var labels = POSITION_LABELS[spread] || {};
            cross.querySelectorAll('.sea-pos-label').forEach(function (el) {
                el.textContent = labels[el.dataset.position] || '';
            });
            // Wipe filled slots — the active position set just changed.
            cross.querySelectorAll('.sea-card-slot.sea-card-slot--filled').forEach(function (slot) {
                var crossing = slot.classList.contains('sea-card-slot--crossing');
                slot.className = 'sea-card-slot sea-card-slot--empty' + (crossing ? ' sea-card-slot--crossing' : '');
                slot.innerHTML = '';
                slot.removeAttribute('data-card-id');
                slot.removeAttribute('data-pos-key');
            });
        }
        window._mySeaApplySpread = _applySpread;   // test seam

        // Seat-ring re-render is shared with the owner's my_sea (DRY) — see
        // `mySeaRenderSeats` in my-sea-seats.js. Pass this viewer's own token so
        // their deposited chair re-marks `--self` on a live re-render.
        function _renderSeats(seats) {
            if (window.mySeaRenderSeats) window.mySeaRenderSeats(seats, MY_SEAT_TOKEN);
        }
        window._mySeaRenderSeats = _renderSeats;   // test seam

        // Reveal a single hand entry on the cross (register + force --visible).
        function _revealCard(cross, e) {
            var card = byId[e.card_id];
            if (!card) return;
            var c = {};
            for (var k in card) { if (Object.prototype.hasOwnProperty.call(card, k)) c[k] = card[k]; }
            c.reversed = !!e.reversed;
            window.SeaDeal.register(c, '.sea-pos-' + e.position, e.polarity === 'levity');
            // register's _fillSlot sets --filled (opacity:0) but not --visible
            // (the owner adds that on stage-dismiss; the spectator has no modal).
            // Add it here so the card lands at its final opacity — matching the
            // refreshed state — instead of racing the empty→filled transition
            // (the long-standing my_sea ease-in/ease-out glitch).
            var cell = cross.querySelector('.sea-pos-' + e.position);
            var filled = cell && cell.querySelector('.sea-card-slot--filled');
            if (filled) filled.classList.add('sea-card-slot--visible');
        }
        // True when this exact card is already shown in its slot — so a
        // cumulative re-broadcast doesn't re-animate cards already on the cross.
        function _isShown(cross, e) {
            var cell = cross.querySelector('.sea-pos-' + e.position);
            var slot = cell && cell.querySelector('.sea-card-slot--filled');
            return !!(slot && String(slot.dataset.cardId) === String(e.card_id));
        }

        // Visitor witnesses cards land ONE AT A TIME. AUTO DRAW commits all six
        // in a single POST (navigate-away safety) → one `sea_draw` carrying the
        // whole hand; revealing them all in that tick made them pop in at once
        // (user-reported 2026-05-30: "async as intended, but not in sequence").
        // So we stagger the freshly-added entries — already-shown cards stay put
        // (a manual single-card add reveals immediately, fresh.length === 1).
        var REVEAL_STAGGER_MS = 420;
        var _revealTimer = null;
        function _applyHand(hand) {
            if (!window.SeaDeal || !window.SeaDeal.register) return;
            var cross = document.querySelector('.my-sea-cross');
            if (!cross) return;
            if (_revealTimer) { clearTimeout(_revealTimer); _revealTimer = null; }
            var inHand = {};
            var fresh = [];
            (hand || []).forEach(function (e) {
                if (!e || !e.position) return;
                inHand[e.position] = true;
                if (!_isShown(cross, e)) fresh.push(e);   // queue only new cards
            });
            // Re-empty any slot the owner cleared (DEL / spread switch).
            cross.querySelectorAll('.sea-card-slot.sea-card-slot--filled').forEach(function (slot) {
                var pos = slot.dataset.posKey;
                if (pos && !inHand[pos]) {
                    var crossing = slot.classList.contains('sea-card-slot--crossing');
                    slot.className = 'sea-card-slot sea-card-slot--empty' + (crossing ? ' sea-card-slot--crossing' : '');
                    slot.innerHTML = '';
                    slot.removeAttribute('data-card-id');
                    slot.removeAttribute('data-pos-key');
                }
            });
            // Reveal fresh entries in draw order (the server stores the hand in
            // DRAW_ORDER), first immediately then one per stagger tick.
            var idx = 0;
            function next() {
                if (idx >= fresh.length) { _revealTimer = null; return; }
                _revealCard(cross, fresh[idx++]);
                _revealTimer = (idx < fresh.length) ? setTimeout(next, REVEAL_STAGGER_MS) : null;
            }
            next();
        }
        window._mySeaApplyHand = _applyHand;   // test seam

        var tries = 0;
        function _connect() {
            var scheme = window.location.protocol === 'https:' ? 'wss' : 'ws';
            var ws;
            try {
                ws = new WebSocket(scheme + '://' + window.location.host + '/ws/my-sea-spectate/{{ owner.id }}/');
            } catch (e) { return; }
            ws.onmessage = function (ev) {
                var msg;
                try { msg = JSON.parse(ev.data); } catch (e) { return; }
                if (!msg) return;
                if (msg.type === 'sea_draw') { _applySpread(msg.spread); _applyHand(msg.hand); }
                else if (msg.type === 'sea_spread') _applySpread(msg.spread);
                else if (msg.type === 'sea_seats') _renderSeats(msg.seats);
            };
            // Brief capped reconnect for transient blips (no infinite loop if
            // the spectate server/Redis is down).
            ws.onclose = function () { if (tries++ < 5) setTimeout(_connect, 3000); };
        }
        if (document.readyState === 'loading') {
            document.addEventListener('DOMContentLoaded', _connect);
        } else {
            _connect();
        }
    }());
    </script>
    {% endif %}
    <script>
    (function () {
        // VIEW DRAW toggles the read-only draw against the table hex. The
        // page's data-phase flips landing↔picker so the cross reuses the
        // owner's `.my-sea-page[data-phase="picker"]` --duoUser felt bg (DRY —
        // same rule the owner picker rides). The hex view stays "landing".
        var page = document.querySelector('.my-sea-page');
        var viewBtn = document.getElementById('id_my_sea_view_draw_btn');
        var draw = document.getElementById('id_my_sea_visit_draw');
        var landing = document.querySelector('.my-sea-visit-landing');
        function _setDraw(show) {
            if (!draw) return;
            draw.style.display = show ? '' : 'none';
            if (landing) landing.style.display = show ? 'none' : '';
            if (page) page.setAttribute('data-phase', show ? 'picker' : 'landing');
        }
        if (viewBtn && draw) {
            viewBtn.addEventListener('click', function () {
                _setDraw(draw.style.display === 'none');
            });
        }

        // Phase-aware NVM (the gear menu's NVM calls this). On the DRAW/spread
        // phase NVM just flips back to the table hex — it stays inside the
        // my_sea_visit ecosphere (voice untouched), so NO guard, like the owner's
        // picker→landing NVM. On the table hex NVM LEAVES to the bud's page, with
        // the shared voice-disconnect guard (mySeaGuardedNav). (user-spec
        // 2026-05-30.)
        var BUD_URL = '{% url 'billboard:bud_page' owner.id %}';
        function _closeGearMenu() {
            var menu = document.getElementById('id_my_sea_menu');
            if (menu) menu.style.display = 'none';
            var gear = document.querySelector('.gear-btn[data-menu-target="id_my_sea_menu"]');
            if (gear) gear.classList.remove('active');
        }
        window.mySeaVisitNvm = function (e) {
            if (page && page.getAttribute('data-phase') === 'picker') {
                if (e && e.preventDefault) e.preventDefault();
                _setDraw(false);     // back to the table hex
                _closeGearMenu();
                return;
            }
            // Table hex → leave to the bud's page (guarded if voice is live).
            if (window.mySeaGuardedNav) window.mySeaGuardedNav(e, BUD_URL);
            else window.location.href = BUD_URL;
        };
        // GATE VIEW → visitor gate.
        var gateBtn = document.getElementById('id_my_sea_gate_view_btn');
        if (gateBtn) {
            gateBtn.addEventListener('click', function () {
                window.location.href = gateBtn.dataset.gateUrl || '#';
            });
        }
    }());
    </script>
{% endblock scripts %}
