var SeaDeal = (function () {
    'use strict';

    var overlay, stage, stageCard, statBlock;
    var spinBtn, fyiBtn, bdrop;
    var fyiPanel, fyiTitle, fyiType, fyiEffect, fyiIndex, fyiPrev, fyiNext;

    var _userPolarity = 'levity';
    // posName (raw, e.g. "cover") → {card, isLevity}. The KEY here MUST
    // match what `_collectHandFromDom` in `my_sea.html` reads (which is
    // `slot.dataset.posKey`, stamped raw by both the template + the
    // inline `_fillSlot` shim). Storing the SELECTOR form ".sea-pos-
    // cover" instead silently broke persistence (manual draws never
    // POSTed) — fixed 2026-05-21.
    var _seaHand      = {};
    // Raw position name (e.g. "cover") of the currently-showing stage.
    // `_hideStage` prefixes `.sea-pos-` when querySelector'ing the cell.
    var _viewingPos   = null;

    // Internal helper — strip the `.sea-pos-` prefix off a selector to
    // get the raw position name. Tolerant of inputs that are already
    // raw (no prefix → returned as-is) so callers don't need to know
    // which form they have.
    function _posName(posSelector) {
        return (posSelector || '').replace(/^\.sea-pos-/, '');
    }
    var _infoData     = [];
    var _infoIdx      = 0;
    var _infoOpen     = false;
    var _spinOrigLabel, _fyiOrigLabel;
    // Timer handles for the reversed-card auto-rotate sequence. Tracked
    // so a re-open mid-rotate (or a stage dismiss + reopen) cancels the
    // pending kickoff / cleanup instead of stacking handlers.
    var _rotateTimer = null, _rotateEndTimer = null;
    // Reversed-card auto-rotate timings. SPIN's CSS transition is 0.4s
    // (_card-deck.scss:2116); the open-rotate is 2× that per user-spec
    // 2026-05-26. The kickoff delay waits for the sea-flip-in keyframe
    // (0.35s) plus a small buffer so the rotate doesn't visually fight
    // the modal's flip-in.
    var SPIN_MS         = 400;
    var AUTO_ROTATE_MS  = 800;
    var FLIP_IN_WAIT_MS = 400;

    // ── Stage card population (delegates to shared StageCard module) ──────────

    function _populate(card, isLevity) {
        var polarity = isLevity ? 'levity' : 'gravity';
        StageCard.populateCard(stageCard, card, polarity);
        StageCard.populateKeywords(statBlock, card.keywords_upright, card.keywords_reversed, {
            uprightSel:  '#id_sea_stat_upright',
            reversedSel: '#id_sea_stat_reversed',
        });
        StageCard.populateStatExtras(statBlock, card);
        _infoData = StageCard.buildInfoData(card);
        _infoIdx  = 0;

        // Stat-block always lands in the card's true reversal state immediately
        // (REVERSAL label highlights as the modal opens). The card itself starts
        // rightside-up regardless of reversal — `_autoRotateToReversed` (kicked
        // off from `_showStage`) handles the slow 180° rotate-in afterwards.
        // Cancel any in-flight prior rotate (re-open during animation).
        stageCard.classList.remove('stage-card--reversed');
        if (_rotateTimer) { clearTimeout(_rotateTimer); _rotateTimer = null; }
        if (_rotateEndTimer) { clearTimeout(_rotateEndTimer); _rotateEndTimer = null; }
        delete stageCard.dataset.spinning;
        stageCard.style.transitionDuration = '';
        statBlock.classList.toggle('is-reversed', !!card.reversed);
        _closeInfo();
    }

    // ── FYI info panel ────────────────────────────────────────────────────────

    function _renderInfo() {
        StageCard.renderFyi(fyiPanel, _infoData, _infoIdx);
    }

    function _openInfo() {
        _infoOpen = true;
        _renderInfo();
        if (fyiPanel) fyiPanel.style.display = 'flex';
        if (spinBtn) { spinBtn.classList.add('btn-disabled'); spinBtn.textContent = '×'; }
        if (fyiBtn)  { fyiBtn.classList.add('btn-disabled');  fyiBtn.textContent  = '×'; }
        statBlock.classList.add('fyi-open');
    }

    function _closeInfo() {
        _infoOpen = false;
        if (fyiPanel) fyiPanel.style.display = 'none';
        if (spinBtn) { spinBtn.classList.remove('btn-disabled'); spinBtn.textContent = _spinOrigLabel || 'SPIN'; }
        if (fyiBtn)  { fyiBtn.classList.remove('btn-disabled');  fyiBtn.textContent  = _fyiOrigLabel  || 'FYI';  }
        if (statBlock) statBlock.classList.remove('fyi-open');
    }

    // ── Slot fill ─────────────────────────────────────────────────────────────

    function _fillSlot(posSelector, card, isLevity) {
        var cell = overlay.querySelector(posSelector);
        if (!cell) return;
        var slot = cell.querySelector('.sea-card-slot');
        if (!slot) return;
        slot.classList.remove('sea-card-slot--empty');
        slot.classList.add('sea-card-slot--filled');
        slot.classList.add(isLevity ? 'sea-card-slot--levity' : 'sea-card-slot--gravity');
        if (card.reversed) slot.classList.add('sea-card-slot--reversed');
        // Long Roman numerals (XVIII / XXIII / XXVIII / XXXIII / XXXVIII /
        // XLIII / XLVIII) need horizontal squeezing to fit the slot — see SCSS.
        if ((card.corner_rank || '').length >= 5) slot.classList.add('sea-card-slot--rank-long');
        slot.dataset.cardId  = String(card.id);
        // Raw position name (e.g. "cover"), NOT the selector form. The
        // my-sea inline `_collectHandFromDom` reads this + looks up by
        // raw name from `_currentOrder()`; the template's empty-slot
        // partial also stamps `data-pos-key="{{ position }}"` raw.
        // Standardize so both code paths agree (2026-05-21 fix).
        slot.dataset.posKey  = _posName(posSelector);
        // Sprint A.7-polish — image-mode branch. When the drawn card carries
        // an `image_url` (deck has_card_images=True, Minchiate today), render
        // an <img> + add the `.sea-card-slot--image` marker class so the
        // shared `_card-deck.scss` comma-list rule applies the contour
        // stroke + depth shadow. Mirrors the server-rendered branch in
        // `_my_sea_slot.html` for saved-hand parity. Otherwise fall through
        // to the legacy corner-rank + suit-icon render.
        if (card.image_url) {
            slot.classList.add('sea-card-slot--image');
            if (card.arcana_key) slot.dataset.arcanaKey = card.arcana_key;
            slot.innerHTML =
                '<img class="sig-stage-card-img" src="' + card.image_url +
                '" alt="' + (card.name || '').replace(/"/g, '&quot;') + '">';
        } else {
            slot.classList.remove('sea-card-slot--image');
            slot.removeAttribute('data-arcana-key');
            slot.innerHTML =
                '<span class="fan-corner-rank">' + card.corner_rank + '</span>' +
                (card.suit_icon ? '<i class="fa-solid ' + card.suit_icon + '"></i>' : '');
        }
    }

    // ── Show / hide stage ─────────────────────────────────────────────────────

    function _showStage(isLevity) {
        stage.style.display = '';
        stage.classList.toggle('sea-stage--levity',  !!isLevity);
        stage.classList.toggle('sea-stage--gravity', !isLevity);
        stageCard.classList.add('sea-stage-card--shown');
        // Reversed cards land rightside-up first (matches the original text-
        // card legibility convention — dimmed upright title + highlighted
        // upside-down reversal title), then slowly rotate 180° to upside-down
        // after the flip-in finishes. Spec 2026-05-26.
        if (statBlock.classList.contains('is-reversed')) {
            _rotateTimer = setTimeout(_autoRotateToReversed, FLIP_IN_WAIT_MS);
        }
    }

    // Drive the reversed-card open's 180° auto-rotate. Mirrors the SPIN-
    // click pattern (kill the keyframe-forwards fill from sea-flip-in,
    // force reflow, then toggle .stage-card--reversed so the static
    // `transition: transform` lerps the rotation) but doubles the
    // duration via an inline transition-duration override. The
    // `data-spinning` marker companions the SCSS rule that hides the
    // FLIP btn during the rotation window — the btn reappears at the
    // visual bottom-left of the rotated card (top-right in the card's
    // local coords, counter-rotated 180°) once cleared, matching the
    // game-kit fan's "FLIP only at bottom-left" precedent.
    function _autoRotateToReversed() {
        _rotateTimer = null;
        if (!stageCard || !statBlock) return;
        if (stageCard.classList.contains('stage-card--reversed')) return;
        if (!statBlock.classList.contains('is-reversed')) return;
        stageCard.classList.remove('sea-stage-card--shown');
        stageCard.getBoundingClientRect();  // flush layout — clean transition start
        stageCard.dataset.spinning = '1';
        stageCard.style.transitionDuration = (AUTO_ROTATE_MS / 1000) + 's';
        stageCard.classList.add('stage-card--reversed');
        _rotateEndTimer = setTimeout(function () {
            _rotateEndTimer = null;
            if (!stageCard) return;
            stageCard.style.transitionDuration = '';
            delete stageCard.dataset.spinning;
        }, AUTO_ROTATE_MS);
    }

    function _hideStage() {
        // Reveal the deposited card in its slot (opacity 0 → 0.6 transition)
        if (_viewingPos) {
            var cell = overlay.querySelector('.sea-pos-' + _viewingPos);
            if (cell) {
                var slot = cell.querySelector('.sea-card-slot--filled');
                if (slot) slot.classList.add('sea-card-slot--visible');
            }
        }
        stage.style.display = 'none';
        stageCard.classList.remove('sea-stage-card--shown');
        if (_rotateTimer)    { clearTimeout(_rotateTimer);    _rotateTimer    = null; }
        if (_rotateEndTimer) { clearTimeout(_rotateEndTimer); _rotateEndTimer = null; }
        delete stageCard.dataset.spinning;
        stageCard.style.transitionDuration = '';
        _viewingPos = null;
        _closeInfo();
    }

    // ── Public API ─────────────────────────────────────────────────────────────

    function openStage(card, posSelector, isLevity) {
        var posName = _posName(posSelector);
        _viewingPos = posName;
        _seaHand[posName] = { card: card, isLevity: isLevity };
        _populate(card, isLevity);
        _fillSlot(posSelector, card, isLevity);
        _showStage(isLevity);
    }

    // Like `openStage` but DOESN'T show the stage modal — used by AUTO
    // DRAW (my_sea.html) to place cards quietly while keeping them
    // clickable later. The overlay click handler reads `_seaHand[pos]`;
    // without an entry here, click silently no-ops (user-reported
    // bug fixed 2026-05-21). Routing slot fill thru SeaDeal's internal
    // `_fillSlot` (instead of the inline shim) also ensures
    // `dataset.posKey` stays consistent w. the manual-FLIP path.
    function register(card, posSelector, isLevity) {
        _seaHand[_posName(posSelector)] = { card: card, isLevity: isLevity };
        _fillSlot(posSelector, card, isLevity);
    }

    // Seed `_seaHand` from outside SeaDeal — used by my-sea's inline
    // IIFE on init to make server-rendered (saved-hand) slots re-
    // clickable after a refresh. Caller walks the filled DOM slots,
    // looks up each card by `data-card-id` against the embedded deck
    // JSON, and passes a `{posName: {card, isLevity}}` map here. Does
    // NOT touch slot DOM — the slots are already filled by the server
    // template; we just need the in-memory lookup to resolve the
    // overlay click handler. Idempotent.
    function seedHand(handByPosName) {
        if (!handByPosName) return;
        Object.keys(handByPosName).forEach(function (posName) {
            _seaHand[posName] = handByPosName[posName];
        });
    }

    // ── Init ──────────────────────────────────────────────────────────────────

    function init() {
        overlay = document.getElementById('id_sea_overlay');
        if (!overlay) return;

        stage     = overlay.querySelector('#id_sea_stage');
        stageCard = stage  && stage.querySelector('.sea-stage-card');
        statBlock = stage  && stage.querySelector('.sea-stat-block');
        if (!stage || !stageCard || !statBlock) return;

        _userPolarity = overlay.dataset.seaUserPolarity || 'levity';

        spinBtn = statBlock.querySelector('.spin-btn');
        fyiBtn  = statBlock.querySelector('.fyi-btn');
        bdrop   = stage.querySelector('.sea-stage-backdrop');

        fyiPanel  = overlay.querySelector('#id_sea_fyi_panel');
        fyiTitle  = fyiPanel && fyiPanel.querySelector('.sig-info-title');
        fyiType   = fyiPanel && fyiPanel.querySelector('.sig-info-type');
        fyiEffect = fyiPanel && fyiPanel.querySelector('.sig-info-effect');
        fyiIndex  = fyiPanel && fyiPanel.querySelector('.sig-info-index');
        fyiPrev   = statBlock.querySelector('.fyi-prev');
        fyiNext   = statBlock.querySelector('.fyi-next');

        _spinOrigLabel = spinBtn ? spinBtn.textContent : 'SPIN';
        _fyiOrigLabel  = fyiBtn  ? fyiBtn.textContent  : 'FYI';

        // SPIN
        if (spinBtn) {
            spinBtn.addEventListener('click', function () {
                if (spinBtn.classList.contains('btn-disabled')) return;
                statBlock.classList.toggle('is-reversed');
                // Remove animation fill, force reflow so the transition has a start state
                stageCard.classList.remove('sea-stage-card--shown');
                stageCard.getBoundingClientRect(); // flush layout
                // `data-spinning` companions the SCSS hide-flip-btn rule for
                // the 0.4s SPIN window so the FLIP btn doesn't ride along to
                // top-right mid-rotate — it reappears at the visual bottom-
                // left of the rotated card after the spin lands. Matches the
                // game-kit fan's "FLIP only at bottom-left" precedent.
                stageCard.dataset.spinning = '1';
                stageCard.classList.toggle('stage-card--reversed');
                setTimeout(function () { delete stageCard.dataset.spinning; }, SPIN_MS);
            });
        }

        // FYI
        if (fyiBtn) {
            fyiBtn.addEventListener('click', function () {
                if (fyiBtn.classList.contains('btn-disabled')) return;
                _infoOpen ? _closeInfo() : _openInfo();
            });
        }

        // Polish-6 — FLIP btn (rotateY 0→90→0 over 500ms, toggle
        // `.is-flipped-to-back` at midpoint). Renders unconditionally per
        // user-spec "allow the FLIP btn everywhere"; no-ops when the card
        // has no `.sig-stage-card-back-img` sibling (back-img only renders
        // server-side for non-polarized image-equipped decks; text-mode +
        // polarized image decks render no back-img + the FLIP is inert).
        // Mirrors `_flipToBackAnimated` shape from my_sign.html / applet.
        var flipBtn = stage.querySelector('.sea-stage-flip-btn');
        if (flipBtn) {
            flipBtn.addEventListener('click', function () {
                if (stageCard.dataset.flipping) return;
                if (!stageCard.querySelector('.sig-stage-card-back-img')) return;
                stageCard.dataset.flipping = '1';
                stageCard.animate([
                    { transform: 'rotateY(0deg)' },
                    { transform: 'rotateY(90deg)', offset: 0.5 },
                    { transform: 'rotateY(0deg)' },
                ], { duration: 500, easing: 'ease' });
                setTimeout(function () {
                    stageCard.classList.toggle('is-flipped-to-back');
                }, 250);
                setTimeout(function () { delete stageCard.dataset.flipping; }, 500);
            });
        }

        // Clicking the FYI panel itself dismisses it (same as sig-select caution)
        if (fyiPanel) {
            fyiPanel.addEventListener('click', function (e) {
                if (!e.target.closest('.fyi-prev') && !e.target.closest('.fyi-next')) {
                    _closeInfo();
                }
            });
        }

        // Clicking the FYI panel closes it (same pattern as sig-select)
        if (fyiPanel) fyiPanel.addEventListener('click', _closeInfo);

        // FYI nav
        if (fyiPrev) {
            fyiPrev.addEventListener('click', function () {
                _infoIdx = (_infoIdx - 1 + _infoData.length) % _infoData.length;
                _renderInfo();
            });
        }
        if (fyiNext) {
            fyiNext.addEventListener('click', function () {
                _infoIdx = (_infoIdx + 1) % _infoData.length;
                _renderInfo();
            });
        }

        // Backdrop dismiss
        if (bdrop) {
            bdrop.addEventListener('click', _hideStage);
        }

        // Overlay click — handle slot focus/open two-step tap pattern
        // (deck-stack _hideOk is registered separately from the inline template script)
        overlay.addEventListener('click', function (e) {
            var slot = e.target.closest('.sea-card-slot--filled');
            if (!slot) {
                // Clicked outside any filled slot — unfocus all
                overlay.querySelectorAll('.sea-card-slot--focused').forEach(function (s) {
                    s.classList.remove('sea-card-slot--focused');
                });
                return;
            }

            var pos = slot.dataset.posKey;
            if (!pos || !_seaHand[pos]) return;

            if (slot.classList.contains('sea-card-slot--focused')) {
                // Second tap/click — dismiss selection glow, open modal
                overlay.querySelectorAll('.sea-card-slot--focused').forEach(function (s) {
                    s.classList.remove('sea-card-slot--focused');
                });
                var h = _seaHand[pos];
                _viewingPos = pos;
                _populate(h.card, h.isLevity);
                _showStage(h.isLevity);
            } else {
                // First tap/click — focus (persist opacity 1)
                overlay.querySelectorAll('.sea-card-slot--focused').forEach(function (s) {
                    s.classList.remove('sea-card-slot--focused');
                });
                slot.classList.add('sea-card-slot--focused');
            }
        });
    }

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

    function resetHand() {
        _seaHand    = {};
        _viewingPos = null;
        _hideStage();
    }

    return {
        openStage:  openStage,
        register:   register,
        seedHand:   seedHand,
        resetHand:  resetHand,
        reinit:     init,    // call after overlay is injected into the DOM
        _testInit: function () {
            overlay = null; stage = null; stageCard = null; statBlock = null;
            spinBtn = null; fyiBtn = null; bdrop = null;
            fyiPanel = null; fyiTitle = null; fyiType = null;
            fyiEffect = null; fyiIndex = null; fyiPrev = null; fyiNext = null;
            _userPolarity = 'levity';
            _seaHand = {}; _viewingPos = null;
            _infoData = []; _infoIdx = 0; _infoOpen = false;
            if (_rotateTimer)    { clearTimeout(_rotateTimer);    _rotateTimer    = null; }
            if (_rotateEndTimer) { clearTimeout(_rotateEndTimer); _rotateEndTimer = null; }
            init();
        },
    };
}());
