(function () {
    var ASSET_BASE = './assets/other/';
    var COMMON_FALLBACK = {
        loading: 'Loading',
        rule: 'rule',
        rewards: 'Rewards',
        balance: 'Balance',
        noDate: 'No more data',
        balanceNotEnough: 'Insufficient balance',
        rewardUnavailable: 'Rewards are not available yet',
        requestFailed: 'Request failed',
    };
    var WHEEL_FALLBACK = {
        classic: 'Classic',
        inLove: 'luxury',
        advanced: 'Advanced',
        historyRecord: 'History Record',
        rewardTips:
            'Congratulations on receiving: A precious gift worth {} gold coins! Already put in the backpack',
        drawAgain: 'Draw again',
        totalRewards: 'total rewards:',
        userAward: 'Congratulations to {1} for receiving the {2} award',
        rule1: '1. Select a wheel and draw with coins.',
        rule2: '2. Rewards are issued to the backpack after a successful draw.',
        rule3: '3. This activity has no relationship with Apple or Google.',
    };
    var TABS = [
        { key: 'classic', labelKey: 'classic' },
        { key: 'luxury', labelKey: 'inLove' },
        { key: 'advanced', labelKey: 'advanced' },
    ];
    var CONFIGS = {
        classic: {
            key: 'classic',
            amountKey: 'luckyBoxDrawAmount',
            enabledKey: 'luckyBoxEnabled',
            pageSize: 10,
            type: 'grid',
            gridClass: 'grid-classic',
            stageClass: 'wheel-classic',
            lightPrefix: 'lucky_box_',
            minSpeed: 2000,
            slotCount: 8,
            sequence: [1, 2, 3, 5, 8, 7, 6, 4, 1, 2, 3, 5, 8, 7, 6, 4, 1],
        },
        luxury: {
            key: 'luxury',
            amountKey: 'middleLuckyBoxDrawAmount',
            enabledKey: 'middleLuckyBoxEnabled',
            pageSize: 20,
            type: 'round',
            gridClass: 'grid-luxury',
            stageClass: 'wheel-luxury',
            lightPrefix: 'lucky_box_tow_',
            hasMarqueeBox: true,
        },
        advanced: {
            key: 'advanced',
            amountKey: 'advancedLuckyBoxDrawAmount',
            enabledKey: 'advancedLuckyBoxEnabled',
            pageSize: 10,
            type: 'grid',
            gridClass: 'grid-advanced',
            stageClass: 'wheel-advanced',
            minSpeed: 1000,
            hasMarqueeBox: true,
            slotCount: 12,
            sequence: [1, 2, 3, 4, 6, 8, 12, 11, 10, 9, 7, 5, 1],
        },
    };

    function query() {
        var params = new URLSearchParams(window.location.search || '');
        var hash = window.location.hash || '';
        if (hash.indexOf('?') >= 0) {
            new URLSearchParams(hash.split('?').slice(1).join('?')).forEach(
                function (value, key) {
                    if (!params.has(key)) params.set(key, value);
                }
            );
        }
        return params;
    }

    function readQuery(name) {
        return query().get(name) || '';
    }

    function toast(message) {
        if (window.HyAppToast && window.HyAppToast.show) {
            window.HyAppToast.show(message);
            return;
        }
        window.alert(message);
    }

    function stateFor() {
        return {
            rewards: [],
            gridItems: [],
            activeIndex: null,
            isSpinning: false,
            spinInterval: null,
            spinSpeed: 50,
            targetIndex: null,
            goldBtn: [
                { name: '1 Time', value: 1 },
                { name: '10 Time', value: 10 },
                { name: '50 Time', value: 50 },
            ],
            activeBtnIndex: 1,
            showRule: false,
            showRecord: false,
            showReward: false,
            currentReward: { rewardValue: 0, rewards: [] },
            drawAmount: 0,
            historyRecord: [],
            roomId: '',
            loading: false,
            finished: true,
            pageNo: 1,
            isInit: false,
            pageLoading: false,
            drawFailed: false,
            currentBackground: 'light',
            backgroundInterval: null,
            rotateDeg: 0,
            rotationTimer: null,
        };
    }

    function envelopeData(response) {
        if (response && Object.prototype.hasOwnProperty.call(response, 'data'))
            return response.data;
        return response;
    }

    function firstPresentValue(source, keys) {
        source = source || {};
        for (var index = 0; index < keys.length; index += 1) {
            var value = source[keys[index]];
            if (value !== undefined && value !== null && value !== '')
                return value;
        }
        return '';
    }

    function normalizeCompareValue(value) {
        return String(value || '')
            .trim()
            .toLowerCase();
    }

    function resolveAssetUrl(url) {
        var value = String(url || '').trim();
        if (!value) return '';
        if (
            /^(https?:)?\/\//i.test(value) ||
            /^(data|blob):/i.test(value) ||
            value.indexOf('/') === 0 ||
            value.indexOf('./') === 0 ||
            value.indexOf('../') === 0
        )
            return value;
        return ASSET_BASE + value;
    }

    function isDynamicAssetUrl(url) {
        var clean = String(url || '')
            .split('?')[0]
            .split('#')[0];
        return /\.(svga|pag|mp4|webm|mov)$/i.test(clean);
    }

    function positiveModulo(value, size) {
        return ((value % size) + size) % size;
    }

    function roundPrizeAngle(index, count) {
        var sectorAngle = 360 / Math.max(1, count || 1);
        // 奖品图标本身按这个角度排布，指针停止也必须使用同一个基准，否则会停在扇区中间而不是奖品上。
        return index * sectorAngle + sectorAngle / 50;
    }

    function normalizeImageObject(source, keys, fallbackKey) {
        var target = Object.assign({}, source || {});
        var image = firstPresentValue(target, keys);
        if (image) target[fallbackKey] = resolveAssetUrl(image);
        return target;
    }

    function normalizeGridItem(item) {
        var target = Object.assign({}, item || {});
        var merchandise = normalizeImageObject(
            target.merchandise || target,
            [
                'coverUrl',
                'cover',
                'resourceCover',
                'avatar',
                'icon',
                'imageUrl',
            ],
            'coverUrl'
        );
        target.merchandise = merchandise;
        return target;
    }

    function parseJSON(value) {
        if (!value || typeof value !== 'string') return {};
        try {
            return JSON.parse(value);
        } catch (error) {
            return {};
        }
    }

    function normalizeWheelReward(item) {
        var target = Object.assign({}, item || {});
        var metadata = parseJSON(
            firstPresentValue(target, ['metadataJson', 'metadata_json'])
        );
        var rewardType = firstPresentValue(target, [
            'rewardType',
            'reward_type',
        ]);
        var metadataResourceId = firstPresentValue(metadata, [
            'resource_id',
            'resourceId',
        ]);
        var rewardId =
            firstPresentValue(target, [
                'rewardId',
                'reward_id',
                'resourceId',
                'resource_id',
            ]) || metadataResourceId;
        var metadataAssetUrl = firstPresentValue(metadata, [
            'asset_url',
            'assetUrl',
        ]);
        var previewUrl =
            firstPresentValue(metadata, ['preview_url', 'previewUrl']) ||
            (isDynamicAssetUrl(metadataAssetUrl) ? '' : metadataAssetUrl) ||
            firstPresentValue(target, [
                'coverUrl',
                'cover',
                'resourceCover',
                'avatar',
                'icon',
                'imageUrl',
                'previewUrl',
                'preview_url',
            ]);
        var animationUrl = firstPresentValue(metadata, [
            'animation_url',
            'animationUrl',
        ]);
        if (
            !isDynamicAssetUrl(animationUrl) &&
            isDynamicAssetUrl(metadataAssetUrl)
        ) {
            animationUrl = metadataAssetUrl;
        }
        var displayName = firstPresentValue(target, [
            'displayName',
            'display_name',
            'name',
            'rewardName',
        ]);
        var rewardCoins = Number(
            firstPresentValue(target, [
                'rewardCoins',
                'reward_coins',
                'value',
            ]) || 0
        );
        var displayValue = Number(
            firstPresentValue(target, [
                'displayValue',
                'display_value',
                'rewardValue',
                'reward_value',
                'value',
                'rewardCoins',
                'reward_coins',
            ]) || 0
        );
        var rewardCount = Number(
            firstPresentValue(target, [
                'rewardCount',
                'reward_count',
                'count',
            ]) || 1
        );

        target.id =
            firstPresentValue(target, ['id', 'tierId', 'tier_id']) ||
            rewardId ||
            displayName;
        target.tierId = firstPresentValue(target, ['tierId', 'tier_id']);
        target.rewardId = rewardId;
        target.rewardConfigId = firstPresentValue(target, [
            'rewardConfigId',
            'reward_config_id',
        ]);
        target.resourceId =
            firstPresentValue(target, ['resourceId', 'resource_id']) ||
            metadataResourceId;
        target.rewardType = rewardType;
        target.value = displayValue || rewardCoins || rewardCount;
        target.displayGoldAmount = target.value;
        target.rewardCount = rewardCount;
        target.count = rewardCount;
        target.name = displayName;
        target.coverUrl = resolveAssetUrl(previewUrl);
        target.animationUrl =
            rewardType === 'coin' ? '' : resolveAssetUrl(animationUrl);
        target.merchandise = {
            name: displayName,
            coverUrl: target.coverUrl,
            animationUrl: target.animationUrl,
        };
        return normalizeGridItem(target);
    }

    function normalizeWheelRewards(data) {
        data = data || {};
        var items = Array.isArray(data)
            ? data
            : data.rewards || data.tiers || (data.config && data.config.tiers);
        return Array.isArray(items)
            ? items.map(normalizeWheelReward).map(normalizeGridItem)
            : [];
    }

    function visibleWheelRewards(data, config) {
        var items = normalizeWheelRewards(data);
        var slotCount = Number(config && config.slotCount);
        // H5 格子数量是固定视觉契约：classic 只有 8 个外圈奖品位，center 是抽奖按钮。
        // 如果旧配置或缓存仍返回第 9 个奖品，前端不渲染多余格子，避免按钮被奖品占位或抽奖动画索引错位。
        return slotCount > 0 ? items.slice(0, slotCount) : items;
    }

    function normalizeRewardItem(item) {
        var target = normalizeImageObject(
            item,
            [
                'coverUrl',
                'cover',
                'resourceCover',
                'avatar',
                'icon',
                'imageUrl',
                'previewUrl',
                'preview_url',
            ],
            'coverUrl'
        );
        var metadata = parseJSON(
            firstPresentValue(target, ['metadataJson', 'metadata_json'])
        );
        var assetUrl =
            firstPresentValue(metadata, ['asset_url', 'assetUrl']) ||
            firstPresentValue(target, ['assetUrl', 'asset_url']);
        if (isDynamicAssetUrl(target.coverUrl)) target.coverUrl = '';
        if (!target.coverUrl && assetUrl && !isDynamicAssetUrl(assetUrl)) {
            target.coverUrl = resolveAssetUrl(assetUrl);
        }
        var animationUrl =
            firstPresentValue(metadata, ['animation_url', 'animationUrl']) ||
            firstPresentValue(target, ['animationUrl', 'animation_url']);
        if (!isDynamicAssetUrl(animationUrl) && isDynamicAssetUrl(assetUrl)) {
            animationUrl = assetUrl;
        }
        target.animationUrl = isDynamicAssetUrl(animationUrl)
            ? resolveAssetUrl(animationUrl)
            : '';
        return target;
    }

    function rewardCountValue(item) {
        var value = Number(
            firstPresentValue(item, ['count', 'rewardCount', 'reward_count']) ||
                0
        );
        return value > 0 ? value : 1;
    }

    function rewardGroupKey(item) {
        item = item || {};
        var metadata = parseJSON(
            firstPresentValue(item, ['metadataJson', 'metadata_json'])
        );
        var metadataResourceId = firstPresentValue(metadata, [
            'resource_id',
            'resourceId',
        ]);
        return [
            firstPresentValue(item, [
                'resourceId',
                'resource_id',
                'rewardId',
                'reward_id',
                'id',
            ]) || metadataResourceId,
            firstPresentValue(item, [
                'rewardType',
                'reward_type',
                'resourceType',
                'resource_type',
            ]) ||
                firstPresentValue(metadata, ['resource_type', 'resourceType']),
            firstPresentValue(item, ['animationUrl', 'animation_url']),
            firstPresentValue(item, [
                'coverUrl',
                'cover_url',
                'previewUrl',
                'preview_url',
                'cover',
            ]),
            firstPresentValue(item, [
                'name',
                'displayName',
                'display_name',
                'rewardName',
                'reward_name',
            ]),
            rewardDisplayValue(item),
        ]
            .map(function (value) {
                return normalizeCompareValue(value);
            })
            .join('|');
    }

    function aggregateRewardItems(items) {
        var map = {};
        var list = [];
        (items || []).forEach(function (item) {
            var key = rewardGroupKey(item);
            var count = rewardCountValue(item);
            if (!map[key]) {
                map[key] = Object.assign({}, item, {
                    count: 0,
                    rewardCount: 0,
                });
                list.push(map[key]);
            }
            map[key].count += count;
            map[key].rewardCount = map[key].count;
        });
        return list;
    }

    function normalizeHistoryRecord(record) {
        var target = Object.assign({}, record || {});
        var rewards = Array.isArray(target.rewards) ? target.rewards : [];
        target.rewards = rewards.map(normalizeRewardItem);
        return target;
    }

    function normalizeHintItem(item) {
        return normalizeImageObject(
            item,
            [
                'avatar',
                'coverUrl',
                'cover',
                'resourceCover',
                'icon',
                'imageUrl',
            ],
            'avatar'
        );
    }

    function prizePreviewSource(item) {
        item = item || {};
        var merchandise = item.merchandise || {};
        var assetUrl =
            firstPresentValue(merchandise, ['assetUrl', 'asset_url']) ||
            firstPresentValue(item, ['assetUrl', 'asset_url']);
        var animationUrl =
            firstPresentValue(merchandise, ['animationUrl', 'animation_url']) ||
            firstPresentValue(item, ['animationUrl', 'animation_url']);
        if (!isDynamicAssetUrl(animationUrl) && isDynamicAssetUrl(assetUrl)) {
            animationUrl = assetUrl;
        }
        var coverUrl =
            firstPresentValue(merchandise, [
                'coverUrl',
                'cover',
                'previewUrl',
                'preview_url',
            ]) ||
            firstPresentValue(item, [
                'coverUrl',
                'cover',
                'resourceCover',
                'previewUrl',
                'preview_url',
                'avatar',
                'icon',
                'imageUrl',
            ]) ||
            (isDynamicAssetUrl(assetUrl) ? '' : assetUrl);
        animationUrl = resolveAssetUrl(animationUrl);
        return {
            animationUrl: isDynamicAssetUrl(animationUrl) ? animationUrl : '',
            coverUrl: resolveAssetUrl(coverUrl),
        };
    }

    function normalizeDrawResult(data) {
        var target = Object.assign({ rewardValue: 0, rewards: [] }, data || {});
        var rewards = Array.isArray(target.rewards) ? target.rewards : [];
        target.rawRewards = rewards.slice();
        if (
            rewards.length === 0 &&
            firstPresentValue(target, [
                'selectedTierId',
                'selected_tier_id',
                'rewardType',
                'reward_type',
                'rewardId',
                'reward_id',
            ])
        ) {
            rewards = [normalizeWheelReward(target)];
            target.rawRewards = [Object.assign({}, data || {})];
        }
        target.rewards = rewards
            .map(normalizeWheelReward)
            .map(normalizeRewardItem);
        target.rewards = aggregateRewardItems(target.rewards);
        if (target.rewards.length > 0) {
            var aggregateValue = target.rewards.reduce(function (sum, item) {
                return sum + rewardDisplayValue(item) * rewardCountValue(item);
            }, 0);
            if (aggregateValue > 0) target.rewardValue = aggregateValue;
        }
        return target;
    }

    function normalizeWalletBalance(payload) {
        var data = envelopeData(payload) || {};
        var list = Array.isArray(data)
            ? data
            : data.balances || data.values || data.list || [];
        var info = Array.isArray(list)
            ? list.find(function (item) {
                  var type = firstPresentValue(item, [
                      'asset_type',
                      'assetType',
                      'walletType',
                      'type',
                  ]);
                  return String(type || '').toUpperCase() === 'COIN';
              })
            : null;
        return info
            ? Number(
                  firstPresentValue(info, [
                      'available_amount',
                      'availableAmount',
                      'balance',
                      'amount',
                  ]) || 0
              )
            : 0;
    }

    function createCommandID(prefix) {
        var random = '';
        if (window.crypto && window.crypto.getRandomValues) {
            var values = new Uint32Array(2);
            window.crypto.getRandomValues(values);
            random = values[0].toString(36) + values[1].toString(36);
        } else {
            random = Math.random().toString(36).slice(2);
        }
        return (
            String(prefix || 'wheel') +
            '_' +
            Date.now().toString(36) +
            '_' +
            random
        );
    }

    function firstDrawReward(drawResult) {
        drawResult = drawResult || {};
        if (
            Array.isArray(drawResult.rawRewards) &&
            drawResult.rawRewards.length > 0
        )
            return drawResult.rawRewards[0];
        if (Array.isArray(drawResult.records) && drawResult.records.length > 0)
            return drawResult.records[0];
        if (Array.isArray(drawResult.rewards) && drawResult.rewards.length > 0)
            return drawResult.rewards[0];
        return null;
    }

    function rewardDisplayValue(reward) {
        var metadata = parseJSON(
            firstPresentValue(reward, ['metadataJson', 'metadata_json'])
        );
        return Number(
            firstPresentValue(reward, [
                'value',
                'displayValue',
                'display_value',
                'displayGoldAmount',
                'goldAmount',
                'rewardValue',
                'reward_value',
                'rewardCoins',
                'reward_coins',
                'amount',
            ]) ||
                firstPresentValue(metadata, [
                    'display_value',
                    'displayValue',
                    'coin_price',
                    'coinPrice',
                    'price',
                    'value',
                ]) ||
                0
        );
    }

    function gridItemDisplayValue(item) {
        return Number(
            firstPresentValue(item, [
                'value',
                'displayGoldAmount',
                'goldAmount',
            ]) || 0
        );
    }

    function resolveRewardTargetIndex(gridItems, drawResult) {
        var reward = firstDrawReward(drawResult);
        if (!reward || !Array.isArray(gridItems) || gridItems.length === 0)
            return 0;
        var rewardMetadata = parseJSON(
            firstPresentValue(reward, ['metadataJson', 'metadata_json'])
        );
        var metadataResourceId = firstPresentValue(rewardMetadata, [
            'resource_id',
            'resourceId',
        ]);
        var rewardId = normalizeCompareValue(
            firstPresentValue(reward, [
                'id',
                'tierId',
                'tier_id',
                'selectedTierId',
                'selected_tier_id',
                'rewardId',
                'reward_id',
                'rewardConfigId',
                'reward_config_id',
                'resourceId',
                'resource_id',
            ]) || metadataResourceId
        );
        var rewardName = normalizeCompareValue(
            firstPresentValue(reward, [
                'name',
                'resourceName',
                'resource_name',
                'rewardName',
                'reward_name',
            ]) ||
                firstPresentValue(rewardMetadata, [
                    'resource_name',
                    'resourceName',
                    'resource_code',
                    'resourceCode',
                ])
        );
        var rewardCover = normalizeCompareValue(
            firstPresentValue(reward, [
                'coverUrl',
                'cover_url',
                'cover',
                'resourceCover',
                'resource_cover',
                'previewUrl',
                'preview_url',
            ]) ||
                firstPresentValue(rewardMetadata, [
                    'preview_url',
                    'previewUrl',
                    'asset_url',
                    'assetUrl',
                ])
        );
        var rewardValue = rewardDisplayValue(reward);

        function matchBy(predicate) {
            for (var index = 0; index < gridItems.length; index += 1) {
                if (predicate(gridItems[index] || {})) return index;
            }
            return -1;
        }

        var byId = rewardId
            ? matchBy(function (item) {
                  return (
                      normalizeCompareValue(
                          firstPresentValue(item, [
                              'id',
                              'rewardId',
                              'reward_id',
                              'rewardConfigId',
                              'reward_config_id',
                              'resourceId',
                              'resource_id',
                          ])
                      ) === rewardId
                  );
              })
            : -1;
        if (byId >= 0) return byId;

        var byCoverValue = rewardCover
            ? matchBy(function (item) {
                  var merchandise = item.merchandise || {};
                  return (
                      normalizeCompareValue(
                          firstPresentValue(merchandise, ['coverUrl', 'cover'])
                      ) === rewardCover &&
                      (!rewardValue ||
                          gridItemDisplayValue(item) === rewardValue)
                  );
              })
            : -1;
        if (byCoverValue >= 0) return byCoverValue;

        var byNameValue = rewardName
            ? matchBy(function (item) {
                  var merchandise = item.merchandise || {};
                  return (
                      normalizeCompareValue(
                          firstPresentValue(merchandise, [
                              'name',
                              'resourceName',
                              'resource_name',
                              'rewardName',
                              'reward_name',
                          ])
                      ) === rewardName &&
                      (!rewardValue ||
                          gridItemDisplayValue(item) === rewardValue)
                  );
              })
            : -1;
        if (byNameValue >= 0) return byNameValue;

        var byValue = rewardValue
            ? matchBy(function (item) {
                  return gridItemDisplayValue(item) === rewardValue;
              })
            : -1;
        return byValue >= 0 ? byValue : 0;
    }

    new Vue({
        el: '#app',
        data: function () {
            return {
                tabs: TABS,
                currentLanguage: 'en',
                currentPage: 'classic',
                userBalance: 0,
                pageLoading: true,
                enabled: {
                    classic: true,
                    luxury: true,
                    advanced: true,
                },
                preview: {
                    open: false,
                    coverUrl: '',
                    animationUrl: '',
                    animationReady: false,
                },
                pages: {
                    classic: stateFor(),
                    luxury: stateFor(),
                    advanced: stateFor(),
                },
            };
        },
        computed: {
            active: function () {
                return this.pages[this.currentPage];
            },
            currentConfig: function () {
                return CONFIGS[this.currentPage];
            },
            pageClass: function () {
                return this.currentConfig.stageClass;
            },
            durationSpeed: function () {
                var count = this.active.rewards.length || 1;
                return 10 * count + 's';
            },
            marqueeRewards: function () {
                return this.active.rewards.concat(this.active.rewards);
            },
            balanceEnough: function () {
                return (
                    this.userBalance >=
                    this.active.goldBtn[this.active.activeBtnIndex].value *
                        this.active.drawAmount
                );
            },
        },
        created: function () {
            this.currentLanguage = this.resolveLanguage();
            document.documentElement.lang = this.currentLanguage;
            document.documentElement.dir =
                this.currentLanguage === 'ar' ? 'rtl' : 'ltr';
            this.currentPage = this.firstPageFromURL();
            this.initRoomID();
            this.loadData();
        },
        beforeDestroy: function () {
            this.closePrizePreview();
            if (this.previewPlayer && this.previewPlayer.destroy) {
                this.previewPlayer.destroy();
            }
            Object.keys(this.pages).forEach(
                function (key) {
                    this.stopTimers(this.pages[key]);
                }.bind(this)
            );
        },
        methods: {
            resolveLanguage: function () {
                if (window.HyAppI18n && window.HyAppI18n.lang)
                    return window.HyAppI18n.lang();
                return readQuery('appLanguage') || readQuery('lang') || 'en';
            },
            t: function (key) {
                var fullKey = 'wheel.' + key;
                var fallback = WHEEL_FALLBACK[key] || fullKey;
                return window.HyAppI18n && window.HyAppI18n.t
                    ? window.HyAppI18n.t(fullKey, fallback)
                    : fallback;
            },
            common: function (key) {
                var fullKey = 'wheel.common.' + key;
                var fallback = COMMON_FALLBACK[key] || key;
                return window.HyAppI18n && window.HyAppI18n.t
                    ? window.HyAppI18n.t(fullKey, fallback)
                    : fallback;
            },
            replaceOne: function (key, value) {
                return this.t(key).replace(/{}/g, value);
            },
            replaceTwo: function (key, first, second) {
                return this.t(key)
                    .replace(/\{1}/g, first)
                    .replace(/\{2}/g, second);
            },
            asset: function (name) {
                return ASSET_BASE + name;
            },
            rewardTotalValue: function (drawResult) {
                drawResult = drawResult || {};
                var directValue = Number(
                    firstPresentValue(drawResult, [
                        'rewardValue',
                        'reward_value',
                        'totalValue',
                        'total_value',
                    ]) || 0
                );
                if (directValue > 0) return directValue;
                // 弹窗展示必须以当前聚合奖品为准：多抽会先合并相同资源，兜底重算可覆盖旧接口未返回总价值的情况。
                return (drawResult.rewards || []).reduce(function (sum, item) {
                    return (
                        sum + rewardDisplayValue(item) * rewardCountValue(item)
                    );
                }, 0);
            },
            formatRewardValue: function (value) {
                var number = Number(value || 0);
                return Number.isFinite(number)
                    ? Math.floor(number).toLocaleString('en-US')
                    : '0';
            },
            rewardTotalText: function (drawResult) {
                return this.formatRewardValue(
                    this.rewardTotalValue(drawResult)
                );
            },
            ensurePreviewPlayer: function () {
                var target = this.$refs.previewEffect;
                if (!target || !window.HyAppEffectPlayer) return null;
                if (
                    this.previewPlayer &&
                    this.previewPlayer.container === target
                )
                    return this.previewPlayer;
                if (this.previewPlayer && this.previewPlayer.destroy) {
                    this.previewPlayer.destroy();
                }
                this.previewPlayer = window.HyAppEffectPlayer.create(target, {
                    activeClass: 'is-active',
                    fit: 'contain',
                    loop: true,
                    muted: true,
                    svgaScript: '../../common/vendor/svga.min.js',
                    waitForWindowLoad: false,
                });
                return this.previewPlayer;
            },
            openPrizePreview: function (item) {
                var source = prizePreviewSource(item);
                if (!source.animationUrl && !source.coverUrl) return;
                this.closePrizePreview();
                this.preview = {
                    open: true,
                    coverUrl: source.coverUrl,
                    animationUrl: source.animationUrl,
                    animationReady: false,
                };
                // 动态资源优先，但必须保留静态图兜底；播放器加载失败时用户仍能看到预览图，不出现空黑层。
                this.$nextTick(function () {
                    if (!this.preview.animationUrl) return;
                    var player = this.ensurePreviewPlayer();
                    if (!player) return;
                    var animationUrl = this.preview.animationUrl;
                    player
                        .play(animationUrl, {
                            svgaScript: '../../common/vendor/svga.min.js',
                            waitForWindowLoad: false,
                        })
                        .then(
                            function () {
                                if (
                                    this.preview.open &&
                                    this.preview.animationUrl === animationUrl
                                ) {
                                    this.preview.animationReady = true;
                                }
                            }.bind(this)
                        )
                        .catch(
                            function () {
                                if (
                                    this.preview.open &&
                                    this.preview.animationUrl === animationUrl
                                ) {
                                    this.preview.animationReady = false;
                                }
                            }.bind(this)
                        );
                });
            },
            closePrizePreview: function () {
                if (this.previewPlayer) this.previewPlayer.stop();
                this.preview = {
                    open: false,
                    coverUrl: '',
                    animationUrl: '',
                    animationReady: false,
                };
            },
            firstPageFromURL: function () {
                var page = readQuery('wheel') || readQuery('tab') || '';
                var hash = window.location.hash || '';
                if (page === 'middle') page = 'luxury';
                if (!page && hash.indexOf('advanced') !== -1) page = 'advanced';
                if (!page && hash.indexOf('middle') !== -1) page = 'luxury';
                return CONFIGS[page] ? page : 'classic';
            },
            initRoomID: function () {
                var roomId = readQuery('roomId') || readQuery('room_id') || '';
                Object.keys(this.pages).forEach(
                    function (key) {
                        this.pages[key].roomId = roomId;
                    }.bind(this)
                );
            },
            wheelAPI: function () {
                if (!window.HyAppAPI || !window.HyAppAPI.activityWheel) {
                    return null;
                }
                return window.HyAppAPI.activityWheel;
            },
            loadData: function () {
                this.pageLoading = true;
                return this.getUserWalletInfo()
                    .then(
                        function () {
                            this.pageLoading = false;
                            this.loadPageData(this.currentPage);
                        }.bind(this)
                    )
                    .catch(
                        function () {
                            // 钱包接口依赖登录态，失败时不能让活动页白屏；后续抽奖仍会由余额判断和服务端扣费共同拦截。
                            this.pageLoading = false;
                            this.loadPageData(this.currentPage);
                        }.bind(this)
                    );
            },
            getUserWalletInfo: function () {
                if (!window.HyAppAPI || !window.HyAppAPI.wallet)
                    return Promise.reject(new Error('HyAppAPI unavailable'));
                return window.HyAppAPI.wallet.balances('COIN').then(
                    function (res) {
                        this.userBalance = normalizeWalletBalance(res);
                    }.bind(this)
                );
            },
            loadPageData: function (page) {
                var state = this.pages[page];
                var config = CONFIGS[page];
                this.configHandle(state, config);
                this.recordsHandle(state, config);
                this.userRecordsHandle(state, config);
            },
            rewardsListHandle: function (state, config) {
                var api = this.wheelAPI();
                if (!api)
                    return Promise.reject(new Error('HyAppAPI unavailable'));
                return api
                    .config(config.key)
                    .then(
                        function (res) {
                            state.isInit = true;
                            var data = envelopeData(res) || {};
                            var drawPrice = Number(
                                firstPresentValue(data, [
                                    'drawPriceCoins',
                                    'draw_price_coins',
                                    'drawPrice',
                                    'draw_price',
                                ]) || 0
                            );
                            if (drawPrice > 0) state.drawAmount = drawPrice;
                            this.enabled[config.key] =
                                firstPresentValue(data, [
                                    'enabled',
                                    config.enabledKey,
                                ]) !== false;
                            // 后端配置来自资源组：金币只要求预览图字段为空也能展示金额，礼物/道具/装扮则统一读取 metadata 的预览图和动态图。
                            state.gridItems = this.enabled[config.key]
                                ? visibleWheelRewards(data, config)
                                : [];
                        }.bind(this)
                    )
                    .catch(function (error) {
                        state.isInit = true;
                        state.gridItems = [];
                        console.warn('wheel rewards load failed', error);
                    });
            },
            drawHandle: function (state, config) {
                var api = this.wheelAPI();
                if (!api) {
                    state.drawFailed = true;
                    this.stopSpinning(state);
                    toast(this.common('requestFailed'));
                    return Promise.resolve();
                }
                return api
                    .draw(config.key, {
                        command_id: createCommandID(config.key),
                        amount:
                            state.goldBtn[state.activeBtnIndex].value *
                            state.drawAmount,
                        times: state.goldBtn[state.activeBtnIndex].value,
                        roomId: state.roomId,
                    })
                    .then(
                        function (res) {
                            var data = normalizeDrawResult(envelopeData(res));
                            state.drawFailed = false;
                            if (data.coin_balance_after !== undefined) {
                                this.userBalance = Number(
                                    data.coin_balance_after || 0
                                );
                            } else {
                                this.getUserWalletInfo();
                            }
                            this.recordsHandle(state, config);
                            state.currentReward = data;
                            state.targetIndex = resolveRewardTargetIndex(
                                state.gridItems,
                                data
                            );
                        }.bind(this)
                    )
                    .catch(
                        function (error) {
                            state.drawFailed = true;
                            this.stopSpinning(state);
                            toast(
                                error.message || this.common('requestFailed')
                            );
                        }.bind(this)
                    );
            },
            recordsHandle: function (state, config) {
                var api = this.wheelAPI();
                if (!api) return Promise.resolve();
                state.loading = true;
                if (state.isInit) state.pageNo += 1;
                return api
                    .history(config.key, {
                        pageNo: state.pageNo,
                        pageSize: config.pageSize,
                    })
                    .then(function (res) {
                        var data = envelopeData(res) || {};
                        var records = Array.isArray(data.records)
                            ? data.records
                            : [];
                        if (records.length === 0) state.finished = true;
                        state.loading = false;
                        // 历史记录和中奖弹窗使用同一批奖品字段，统一标准化后再追加，分页加载不会把裸文件名带进 DOM。
                        state.historyRecord = state.historyRecord.concat(
                            records.map(normalizeHistoryRecord)
                        );
                    })
                    .catch(function () {
                        state.loading = false;
                        state.finished = true;
                    });
            },
            userRecordsHandle: function (state, config) {
                var api = this.wheelAPI();
                if (!api) return Promise.resolve();
                return api
                    .hints(config.key)
                    .then(function (res) {
                        var data = envelopeData(res) || {};
                        if (Array.isArray(data.values))
                            state.rewards = data.values.map(normalizeHintItem);
                    })
                    .catch(function () {
                        state.rewards = [];
                    });
            },
            configHandle: function (state, config) {
                return this.rewardsListHandle(state, config).then(
                    function () {
                        if (!this.enabled[this.currentPage])
                            this.handleSelect(this.firstEnabledPage());
                    }.bind(this)
                );
            },
            firstEnabledPage: function () {
                for (var index = 0; index < TABS.length; index += 1) {
                    if (this.enabled[TABS[index].key]) return TABS[index].key;
                }
                return 'classic';
            },
            handleGoldBtn: function (index) {
                this.active.activeBtnIndex = index;
            },
            handleSelect: function (tab) {
                if (!CONFIGS[tab] || !this.enabled[tab]) return;
                this.currentPage = tab;
                if (!this.pages[tab].gridItems.length) this.loadPageData(tab);
            },
            startLottery: function () {
                var state = this.active;
                var config = this.currentConfig;
                if (!state.gridItems.length || state.drawAmount <= 0) {
                    toast(this.common('rewardUnavailable'));
                    return;
                }
                if (!this.balanceEnough) {
                    toast(this.common('balanceNotEnough'));
                    return;
                }
                if (state.isSpinning) return;
                state.showReward = false;
                state.drawFailed = false;
                state.isInit = false;
                state.finished = false;
                state.pageNo = 1;
                state.historyRecord = [];
                state.targetIndex = null;
                state.activeIndex = null;
                if (config.type === 'round') this.backgroundChange(state);
                this.startSpinning(state, config);
                this.drawHandle(state, config);
                this.stopShowReward(state, config);
            },
            startSpinning: function (state, config) {
                if (config.type === 'round') {
                    this.startRoundSpinning(state);
                    return;
                }
                state.isSpinning = true;
                var sequenceIndex = 0;
                var spinCount = 0;
                var maxFastSpins = 25;
                var speedIncrement = 100;
                var minSpeed = config.minSpeed;
                state.spinInterval = window.setInterval(
                    function () {
                        state.activeIndex = config.sequence[sequenceIndex] - 1;
                        sequenceIndex =
                            (sequenceIndex + 1) % config.sequence.length;
                        spinCount += 1;
                        if (config.key === 'classic') {
                            state.currentBackground =
                                state.currentBackground === 'light'
                                    ? 'light2'
                                    : 'light';
                        }
                        if (
                            spinCount > maxFastSpins &&
                            state.targetIndex !== null
                        ) {
                            state.spinSpeed += speedIncrement;
                            if (
                                Math.abs(
                                    state.activeIndex - state.targetIndex
                                ) <= 4
                            )
                                state.spinSpeed += speedIncrement * 2;
                            if (
                                state.activeIndex === state.targetIndex &&
                                state.spinSpeed >= minSpeed
                            )
                                this.stopSpinning(state);
                        }
                    }.bind(this),
                    state.spinSpeed
                );
            },
            startRoundSpinning: function (state) {
                var startWait = Date.now();
                var waitStartDeg = state.rotateDeg;
                state.isSpinning = true;
                if (state.rotationTimer)
                    window.cancelAnimationFrame(state.rotationTimer);

                var animateToTarget = function () {
                    var count = Math.max(1, state.gridItems.length || 1);
                    var targetIndex = Number(state.targetIndex || 0);
                    var duration = 4200;
                    var startTime = Date.now();
                    var startDeg = state.rotateDeg;
                    var currentDeg = positiveModulo(startDeg, 360);
                    var targetBaseDeg = roundPrizeAngle(targetIndex, count);
                    var forwardDelta = positiveModulo(
                        targetBaseDeg - currentDeg,
                        360
                    );
                    // 指针转盘必须等接口给出真实 targetIndex 后，从当前角度继续正向缓停到奖品图标角度；不能归零或使用假结果。
                    var targetDeg = startDeg + 360 * 6 + forwardDelta;
                    var animate = function () {
                        var elapsed = Date.now() - startTime;
                        var progress = Math.min(1, elapsed / duration);
                        var easedProgress = this.roundStopEase(progress);
                        state.rotateDeg =
                            startDeg + (targetDeg - startDeg) * easedProgress;
                        if (progress >= 1) {
                            state.isSpinning = false;
                            state.rotateDeg = targetDeg;
                            state.rotationTimer = null;
                            this.showDrawReward(state);
                            return;
                        }
                        state.rotationTimer =
                            window.requestAnimationFrame(animate);
                    }.bind(this);
                    animate();
                }.bind(this);

                var waitForTarget = function () {
                    if (
                        state.targetIndex !== null &&
                        state.targetIndex !== undefined
                    ) {
                        animateToTarget();
                        return;
                    }
                    state.rotateDeg =
                        waitStartDeg + ((Date.now() - startWait) / 1000) * 720;
                    state.rotationTimer =
                        window.requestAnimationFrame(waitForTarget);
                };
                waitForTarget();
            },
            customEase: function (value) {
                return value < 0.5
                    ? 2 * value * value
                    : 1 - Math.pow(-2 * value + 2, 3) / 2;
            },
            roundStopEase: function (value) {
                return 1 - Math.pow(1 - value, 4);
            },
            backgroundChange: function (state) {
                state.backgroundInterval = window.setInterval(function () {
                    state.currentBackground =
                        state.currentBackground === 'light'
                            ? 'light2'
                            : 'light';
                }, 100);
            },
            showDrawReward: function (state) {
                if (state.drawFailed || state.targetIndex === null) return;
                if (
                    state.targetIndex !== null &&
                    state.targetIndex !== undefined &&
                    typeof state.activeIndex === 'number'
                ) {
                    state.activeIndex = state.targetIndex;
                }
                state.showReward = true;
                state.isInit = true;
                window.clearInterval(state.backgroundInterval);
            },
            stopShowReward: function (state, config) {
                if (config && config.type === 'round') return;
                window.setTimeout(
                    function () {
                        this.showDrawReward(state);
                    }.bind(this),
                    3300
                );
            },
            stopSpinning: function (state) {
                this.stopTimers(state);
                state.isSpinning = false;
                state.spinSpeed = 50;
                state.activeIndex =
                    state.targetIndex !== null &&
                    state.targetIndex !== undefined
                        ? state.targetIndex
                        : null;
            },
            stopTimers: function (state) {
                window.clearInterval(state.spinInterval);
                window.clearInterval(state.backgroundInterval);
                if (state.rotationTimer)
                    window.cancelAnimationFrame(state.rotationTimer);
                state.spinInterval = null;
                state.rotationTimer = null;
            },
            getPrizePosition: function (index) {
                var count = Math.max(1, this.active.gridItems.length || 1);
                var radius = 150;
                var center = { x: 50, y: 50 };
                var itemAngle = roundPrizeAngle(index, count);
                var angle = (itemAngle * Math.PI) / 180;
                var distance = radius * 0.25;
                var x = center.x + Math.sin(angle) * distance;
                var y = center.y - Math.cos(angle) * distance;
                var textRotation = itemAngle;
                return {
                    left: x + '%',
                    top: y + '%',
                    transform:
                        'translate(-50%, -50%) rotate(' + textRotation + 'deg)',
                };
            },
            buttonStyle: function (index) {
                if (this.currentPage !== 'luxury') return {};
                var name =
                    'lucky_box_tow_' +
                    (index === this.active.activeBtnIndex ? '' : 'un') +
                    'btn' +
                    (index + 1) +
                    '.png';
                return { backgroundImage: 'url(' + this.asset(name) + ')' };
            },
            pointerStyle: function () {
                if (this.currentPage !== 'luxury') return {};
                return { '--pointer-rotate': this.active.rotateDeg + 'deg' };
            },
            formatDate: function (time, format) {
                if (!time) return '';
                var date = new Date(
                    Number(time) || String(time).replace(/-/g, '/')
                );
                function zero(value) {
                    return (value < 10 ? '0' : '') + value;
                }
                return (format || 'yyyy-MM-dd HH:mm:ss').replace(
                    /yyyy|MM|dd|HH|mm|ss/g,
                    function (token) {
                        if (token === 'yyyy') return zero(date.getFullYear());
                        if (token === 'MM') return zero(date.getMonth() + 1);
                        if (token === 'dd') return zero(date.getDate());
                        if (token === 'HH') return zero(date.getHours());
                        if (token === 'mm') return zero(date.getMinutes());
                        return zero(date.getSeconds());
                    }
                );
            },
        },
    });
})();
