(function () {
    var VALID_TABS = ['cp', 'brother', 'sister'];
    var PARTS_BASE = './assets/parts/';
    var PART_NAMES = [
        'bg',
        'avatar',
        'heart',
        'button',
        'xp-track',
        'xp-fill',
        'level-pill',
    ];
    var LEVEL_PARTS = {};

    for (var partLevel = 1; partLevel <= 5; partLevel += 1) {
        LEVEL_PARTS[partLevel] = {};
        PART_NAMES.forEach(function (partName) {
            LEVEL_PARTS[partLevel][partName] =
                PARTS_BASE + 'cp-lv' + partLevel + '-' + partName + '@2x.webp';
        });
    }

    var page = document.getElementById('page');
    var contentPanel = document.getElementById('contentPanel');
    var backButton = document.getElementById('backButton');
    var tabButtons = Array.prototype.slice.call(
        document.querySelectorAll('.tab-button[data-tab]')
    );

    var state = {
        activeTab: normalizeTab(readParam('tab')) || 'cp',
        requestSeq: 0,
        cancelingId: '',
        tabs: {
            cp: createTabState(),
            brother: createTabState(),
            sister: createTabState(),
        },
    };

    function createTabState() {
        return {
            loading: false,
            loaded: false,
            error: '',
            relationships: [],
        };
    }

    function readParam(name) {
        var params = new URLSearchParams(window.location.search || '');
        return params.get(name);
    }

    function normalizeTab(value) {
        var tab = String(value || '')
            .trim()
            .toLowerCase();
        return VALID_TABS.indexOf(tab) >= 0 ? tab : '';
    }

    function normalizeLevel(value) {
        var level = Number(value);
        return Number.isFinite(level) && level >= 1 && level <= 5
            ? Math.floor(level)
            : 1;
    }

    function readNumber(value) {
        var number = Number(value);
        return Number.isFinite(number) ? number : 0;
    }

    function readString(value) {
        return String(value == null ? '' : value).trim();
    }

    function t(key, fallback) {
        if (window.HyAppI18n && window.HyAppI18n.t) {
            return window.HyAppI18n.t(key, fallback);
        }
        return fallback || key;
    }

    function escapeHTML(value) {
        return String(value == null ? '' : value)
            .replace(/&/g, '&amp;')
            .replace(/</g, '&lt;')
            .replace(/>/g, '&gt;')
            .replace(/"/g, '&quot;')
            .replace(/'/g, '&#39;');
    }

    function showToast(message) {
        if (window.HyAppToast && window.HyAppToast.show) {
            window.HyAppToast.show(message);
            return;
        }
        window.dispatchEvent(
            new CustomEvent('hyapp:toast', { detail: { message: message } })
        );
    }

    function profileFromData(data) {
        var json = data || {};
        return {
            userId: readString(json.user_id || json.id),
            displayUserId: readString(json.display_user_id),
            username: readString(json.username || json.nickname || json.name),
            avatar: readString(
                json.avatar ||
                    json.avatar_url ||
                    json.avatar_path ||
                    (json.profile && json.profile.avatar)
            ),
        };
    }

    function displayName(profile) {
        return (
            readString(profile.username) ||
            readString(profile.displayUserId) ||
            readString(profile.userId) ||
            t('cpSpace.unknownUser', 'User')
        );
    }

    function daysFromFormedAt(formedAtMs) {
        var formedAt = readNumber(formedAtMs);
        if (formedAt <= 0) return 0;
        var now = Date.now();
        if (now <= formedAt) return 1;
        return Math.floor((now - formedAt) / 86400000) + 1;
    }

    function currentLevelExp(item) {
        var total = Math.max(0, readNumber(item.intimacy_value));
        var currentThreshold = readNumber(item.current_level_threshold);
        if (currentThreshold <= 0 || currentThreshold >= total) return total;
        return total - currentThreshold;
    }

    function nextLevelExp(item) {
        if (item.max_level) return 0;
        var nextThreshold = readNumber(item.next_level_threshold);
        var currentThreshold = readNumber(item.current_level_threshold);
        if (nextThreshold > 0) {
            if (currentThreshold > 0 && nextThreshold > currentThreshold) {
                return nextThreshold - currentThreshold;
            }
            return nextThreshold;
        }
        var total = Math.max(0, readNumber(item.intimacy_value));
        var remaining = readNumber(item.needed_for_next_level);
        return remaining > 0 ? total + remaining : 0;
    }

    function progressPercent(item) {
        if (item.max_level) return 100;
        var explicit = readNumber(item.level_progress_percent);
        if (explicit > 0) return Math.max(0, Math.min(100, explicit));
        var next = nextLevelExp(item);
        if (next <= 0) return 0;
        return Math.max(0, Math.min(100, (currentLevelExp(item) / next) * 100));
    }

    function normalizeRelationship(item, tab) {
        var relationType = normalizeTab(item && item.relation_type) || tab;
        var level = normalizeLevel(item && item.level);
        var me = profileFromData(item && item.me);
        var partner = profileFromData(item && item.partner);
        var currentExp = currentLevelExp(item || {});
        var nextExp = nextLevelExp(item || {});
        return {
            relationshipId: readString(item && item.relationship_id),
            relationType: relationType,
            status: readString(item && item.status) || 'active',
            level: level,
            me: me,
            partner: partner,
            days: daysFromFormedAt(item && item.formed_at_ms),
            intimacyValue: Math.max(0, readNumber(item && item.intimacy_value)),
            currentExp: currentExp,
            nextExp: nextExp,
            progressPercent: progressPercent(item || {}),
            maxLevel: Boolean(item && item.max_level),
            breakupCostCoins: Math.max(
                0,
                readNumber(item && item.breakup_cost_coins)
            ),
        };
    }

    function activeRelationships(data, tab) {
        var list = Array.isArray(data && data.relationships)
            ? data.relationships
            : [];
        return list
            .map(function (item) {
                return normalizeRelationship(item || {}, tab);
            })
            .filter(function (item) {
                return (
                    item.relationshipId &&
                    item.relationType === tab &&
                    item.status === 'active'
                );
            });
    }

    function setActiveTab(tab) {
        var nextTab = normalizeTab(tab) || 'cp';
        state.activeTab = nextTab;
        page.setAttribute('data-tab', nextTab);
        tabButtons.forEach(function (button) {
            button.classList.toggle(
                'is-active',
                button.getAttribute('data-tab') === nextTab
            );
        });
        render();
        if (!state.tabs[nextTab].loaded && !state.tabs[nextTab].loading) {
            loadTab(nextTab);
        }
    }

    function visualLevel(item) {
        return item.relationType === 'cp' ? item.level : 1;
    }

    function partsFor(item) {
        return LEVEL_PARTS[visualLevel(item)] || LEVEL_PARTS[1];
    }

    function nextLevelLabel(item) {
        if (item.maxLevel) return 'Max';
        return t('cpSpace.levelValue', 'Lv.{level}').replace(
            '{level}',
            String(Math.min(5, item.level + 1))
        );
    }

    function expLabel(item) {
        if (item.maxLevel || item.nextExp <= 0) {
            return t('cpSpace.maxLevelExp', '{exp} EXP').replace(
                '{exp}',
                formatNumber(item.intimacyValue)
            );
        }
        return t('cpSpace.expProgress', '{current}/{next} EXP')
            .replace('{current}', formatNumber(item.currentExp))
            .replace('{next}', formatNumber(item.nextExp));
    }

    function formatNumber(value) {
        var number = Math.max(0, Math.floor(readNumber(value)));
        return String(number).replace(/\B(?=(\d{3})+(?!\d))/g, ',');
    }

    function avatarHTML(profile, side, frameSrc) {
        var name = displayName(profile);
        var avatar = readString(profile.avatar);
        return [
            '<figure class="relation-avatar relation-avatar--',
            side,
            '">',
            avatar
                ? [
                      '<img class="relation-avatar-photo" src="',
                      escapeHTML(avatar),
                      '" alt="',
                      escapeHTML(name),
                      '" />',
                  ].join('')
                : [
                      '<div class="relation-avatar-photo relation-avatar-photo--placeholder" aria-label="',
                      escapeHTML(name),
                      '">',
                      escapeHTML(name.charAt(0).toUpperCase() || '?'),
                      '</div>',
                  ].join(''),
            '<img class="relation-avatar-frame" src="',
            escapeHTML(frameSrc),
            '" alt="" draggable="false" />',
            '<figcaption class="relation-name relation-name--',
            side,
            '">',
            escapeHTML(name),
            '</figcaption>',
            '</figure>',
        ].join('');
    }

    function cardHTML(item, index) {
        var parts = partsFor(item);
        var label = t('cpSpace.levelCard', 'Best friend level {level}').replace(
            '{level}',
            String(item.level)
        );
        var daysText = t('cpSpace.daysValue', '{days} Days').replace(
            '{days}',
            String(item.days)
        );
        var levelTitle = t('cpSpace.levelValue', 'Lv.{level}').replace(
            '{level}',
            String(item.level)
        );
        var cardClass =
            'relation-card relation-card--' + escapeHTML(item.relationType);
        if (item.relationType !== 'cp') cardClass += ' relation-card--shared';

        return [
            '<article class="',
            cardClass,
            '" data-level="',
            visualLevel(item),
            '">',
            '<img class="relation-card-bg" src="',
            parts.bg,
            '" alt="',
            escapeHTML(label),
            '" draggable="false" />',
            avatarHTML(item.me, 'left', parts.avatar),
            avatarHTML(item.partner, 'right', parts.avatar),
            '<img class="relation-heart-art" src="',
            parts.heart,
            '" alt="" draggable="false" />',
            '<div class="relation-days"><strong>',
            escapeHTML(String(item.days)),
            '</strong><span>',
            escapeHTML(t('cpSpace.daysUnit', 'Days')),
            '</span></div>',
            '<div class="relation-level relation-level--current"><img src="',
            parts['level-pill'],
            '" alt="" draggable="false" /><span>',
            escapeHTML(levelTitle),
            '</span></div>',
            '<div class="relation-level relation-level--next"><img src="',
            parts['level-pill'],
            '" alt="" draggable="false" /><span>',
            escapeHTML(nextLevelLabel(item)),
            '</span></div>',
            '<div class="relation-exp">',
            escapeHTML(expLabel(item)),
            '</div>',
            '<div class="relation-progress" aria-label="',
            escapeHTML(daysText),
            '"><img class="relation-progress-track" src="',
            parts['xp-track'],
            '" alt="" draggable="false" /><span style="width:',
            item.progressPercent,
            '%"><img class="relation-progress-fill" src="',
            parts['xp-fill'],
            '" alt="" draggable="false" /></span></div>',
            item.relationType === 'cp'
                ? [
                      '<button class="cancel-button" type="button" data-cancel-index="',
                      index,
                      '"',
                      state.cancelingId === item.relationshipId
                          ? ' disabled'
                          : '',
                      '>',
                      '<img class="cancel-button-art" src="',
                      parts.button,
                      '" alt="" draggable="false" />',
                      '<span>',
                      escapeHTML(
                          state.cancelingId === item.relationshipId
                              ? t('cpSpace.canceling', 'Canceling...')
                              : t('cpSpace.cancel', 'Cancel')
                      ),
                      '</span>',
                      '</button>',
                  ].join('')
                : '',
            '</article>',
        ].join('');
    }

    function render() {
        var tabState = state.tabs[state.activeTab];
        if (tabState.loading && !tabState.loaded) {
            contentPanel.innerHTML =
                '<div class="empty-state">' +
                escapeHTML(t('cpSpace.loading', 'Loading...')) +
                '</div>';
            return;
        }
        if (tabState.error) {
            contentPanel.innerHTML =
                '<button class="empty-state empty-state--button" type="button" data-retry-tab="' +
                escapeHTML(state.activeTab) +
                '">' +
                escapeHTML(tabState.error) +
                '</button>';
            return;
        }
        if (!tabState.relationships.length) {
            contentPanel.innerHTML =
                '<div class="empty-state">' +
                escapeHTML(t('cpSpace.empty', 'No relationship data yet.')) +
                '</div>';
            return;
        }

        contentPanel.innerHTML =
            '<div class="relation-list">' +
            tabState.relationships.map(cardHTML).join('') +
            '</div>';
    }

    function loadTab(tab) {
        var tabState = state.tabs[tab];
        var seq = ++state.requestSeq;
        tabState.loading = true;
        tabState.error = '';
        render();

        // 关系事实全部从 gateway profile 接口读取；H5 只负责按 relation_type 分页拉取，不本地推断用户是否有 CP/兄弟/姐妹。
        return window.HyAppAPI.cpSpace
            .relationships({
                relation_type: tab,
                page: 1,
                page_size: tab === 'cp' ? 1 : 20,
            })
            .then(function (data) {
                if (seq < state.requestSeq && tab === state.activeTab) return;
                tabState.relationships = activeRelationships(data, tab);
                tabState.loaded = true;
            })
            .catch(function (error) {
                tabState.error =
                    (error && error.message) ||
                    t('cpSpace.loadFailed', 'Load failed. Tap to retry.');
            })
            .then(function () {
                tabState.loading = false;
                render();
            });
    }

    function buildCommandId(relationshipId) {
        var random =
            window.crypto && window.crypto.randomUUID
                ? window.crypto.randomUUID()
                : String(Date.now()) + '_' + Math.random().toString(16).slice(2);
        return ['h5_cp_break', relationshipId, random]
            .join('_')
            .replace(/[^A-Za-z0-9_-]/g, '_');
    }

    function confirmBreakup(item) {
        var message =
            item.breakupCostCoins > 0
                ? t(
                      'cpSpace.cancelConfirmWithCost',
                      'Canceling this CP will cost {coins} coins. Continue?'
                  ).replace('{coins}', formatNumber(item.breakupCostCoins))
                : t(
                      'cpSpace.cancelConfirmFree',
                      'Cancel this CP relationship?'
                  );
        return Promise.resolve(window.confirm(message));
    }

    function handleCancel(index) {
        var item =
            state.tabs[state.activeTab].relationships[Number(index)] || null;
        if (!item || !item.relationshipId || state.cancelingId) return;

        confirmBreakup(item).then(function (confirmed) {
            if (!confirmed) return;
            state.cancelingId = item.relationshipId;
            render();

            // command_id 必须由本次点击生成并随请求提交，服务端用它串联占位、钱包扣费和确认解除，网络重试不会重复扣费。
            window.HyAppAPI.cpSpace
                .breakRelationship(item.relationshipId, {
                    command_id: buildCommandId(item.relationshipId),
                })
                .then(function (data) {
                    showToast(t('cpSpace.cancelSuccess', 'Canceled.'));
                    if (window.HyAppBridge && window.HyAppBridge.post) {
                        window.HyAppBridge.post('cpSpaceRelationCanceled', {
                            relationship_id: item.relationshipId,
                            relation_type: item.relationType,
                            breakup_cost_coins: readNumber(
                                data && data.breakup_cost_coins
                            ),
                        });
                    }
                    state.tabs.cp.loaded = false;
                    return loadTab('cp');
                })
                .catch(function (error) {
                    showToast(
                        (error && error.message) ||
                            t('cpSpace.cancelFailed', 'Cancel failed.')
                    );
                })
                .then(function () {
                    state.cancelingId = '';
                    render();
                });
        });
    }

    function applyExternalData(payload) {
        var data = payload || {};
        VALID_TABS.forEach(function (tab) {
            var raw = data[tab];
            if (!Array.isArray(raw)) return;
            state.tabs[tab].relationships = raw
                .map(function (item) {
                    return normalizeRelationship(item || {}, tab);
                })
                .filter(function (item) {
                    return item.relationshipId;
                });
            state.tabs[tab].loaded = true;
        });
        if (normalizeTab(data.activeTab)) state.activeTab = data.activeTab;
        setActiveTab(state.activeTab);
    }

    function bindEvents() {
        if (backButton) {
            backButton.addEventListener('click', function () {
                if (window.HyAppBridge && window.HyAppBridge.back) {
                    window.HyAppBridge.back();
                    return;
                }
                window.history.back();
            });
        }

        tabButtons.forEach(function (button) {
            button.addEventListener('click', function () {
                setActiveTab(button.getAttribute('data-tab'));
            });
        });

        contentPanel.addEventListener('click', function (event) {
            var cancelTarget = event.target.closest('[data-cancel-index]');
            if (cancelTarget) {
                handleCancel(cancelTarget.getAttribute('data-cancel-index'));
                return;
            }
            var retryTarget = event.target.closest('[data-retry-tab]');
            if (retryTarget) loadTab(retryTarget.getAttribute('data-retry-tab'));
        });

        window.addEventListener('hyapp:cp-space-data', function (event) {
            applyExternalData(event.detail || {});
        });

        window.addEventListener('hyapp:i18n-ready', render);
    }

    bindEvents();
    if (window.HyAppCPSpaceData) applyExternalData(window.HyAppCPSpaceData);
    else setActiveTab(state.activeTab);

    if (window.HyAppBridge && window.HyAppBridge.ready) {
        window.HyAppBridge.ready({ page: 'cp-space' });
    }
})();
