(() => {
    "use strict";

    const MOD_NAME = "DizaliaBattlePass";
    const STATE_VERSION = 1;
    const config = Alloy.Resources.parseConfig(MOD_NAME);
    if (!config.enabled) return;

    const TIER_NAMES = ["", "Обычный", "Необычный", "Редкий", "Сверхредкий", "Эпический", "Мифический", "Легендарный"];
    const TIER_TAGS = ["", "Common", "Uncommon", "Rare", "SuperRare", "Epic", "Mythic", "Legendary"];
    const TIER_COLORS = ["#ffffff", "#b8c0cc", "#69cad2", "#5bd98c", "#62a8ff", "#bd78ff", "#ff69cc", "#ffd35f"];
    const TRACK_LABELS = { free: "БЕСПЛАТНО", premium: "PREMIUM" };
    const runtime = {
        ready: false,
        granting: false,
        pools: new Map(),
        questHooksInstalled: false
    };

    function group(name) {
        return config[name] || {};
    }

    function setting(name, key, fallback) {
        const value = group(name)[key];
        return value === undefined || value === null || value === "" ? fallback : value;
    }

    function numberSetting(name, key, fallback) {
        const value = Number(setting(name, key, fallback));
        return Number.isFinite(value) ? value : fallback;
    }

    function booleanSetting(name, key, fallback) {
        const value = group(name)[key];
        return value === undefined ? fallback : !!value;
    }

    function clamp(value, min, max) {
        return Math.max(min, Math.min(max, value));
    }

    function maxLevel() {
        return Math.max(1, Math.floor(numberSetting("season", "maxLevel", 50)));
    }

    function xpPerLevel() {
        return Math.max(1, Math.floor(numberSetting("season", "xpPerLevel", 100)));
    }

    function requiredXp() {
        return maxLevel() * xpPerLevel();
    }

    function now() {
        return Date.now();
    }

    function hashSeed(text) {
        let hash = 2166136261 >>> 0;
        for (let index = 0; index < text.length; index++) {
            hash ^= text.charCodeAt(index);
            hash = Math.imul(hash, 16777619);
        }
        return hash >>> 0;
    }

    function seededRandom(seed) {
        let value = seed >>> 0;
        return () => {
            value += 0x6D2B79F5;
            let result = value;
            result = Math.imul(result ^ result >>> 15, result | 1);
            result ^= result + Math.imul(result ^ result >>> 7, result | 61);
            return ((result ^ result >>> 14) >>> 0) / 4294967296;
        };
    }

    function randomInteger(random, min, max) {
        return min + Math.floor(random() * (max - min + 1));
    }

    function tierOf(object) {
        const note = String(object && object.note || "");
        for (let tier = 7; tier >= 1; tier--) {
            if (new RegExp(`<${TIER_TAGS[tier]}>`, "i").test(note)) return tier;
        }
        return 0;
    }

    function rewardKind(object) {
        if (DataManager.isWeapon(object)) return "weapon";
        if (DataManager.isArmor(object)) return "armor";
        return "item";
    }

    function databaseFor(kind) {
        if (kind === "weapon") return $dataWeapons;
        if (kind === "armor") return $dataArmors;
        return $dataItems;
    }

    function databaseObject(reward) {
        const data = databaseFor(reward.kind);
        return data && data[reward.id];
    }

    function safeRewardObject(object) {
        if (!object || !object.name || !tierOf(object)) return false;
        const note = String(object.note || "");
        const name = String(object.name || "");
        const gachaCurrency = /<DizaliaGachaCurrency>/i.test(note);
        if (/<(?:NoBattlePass|KeyItem|QuestItem|StoryItem|CharacterItem)>/i.test(note)) return false;
        if (/<NoGacha>/i.test(note) && !gachaCurrency) return false;
        if (/<(?:главн(?:ый|ая) предмет|квестов(?:ый|ая) предмет|сюжетн(?:ый|ая) предмет)>/i.test(note)) return false;
        if (object.itypeId === 2) return false;
        if (/нимб/i.test(name)) return false;
        if (/ключ(?:и)?(?:\s|$)|документ|лицензия|нижн(?:ее|яя) бель|трус|стринг/i.test(name)) return false;
        if (/(?:люка|роузи|винстона|реверса|алекса|нортума|деуса|серы|югеля|мио|трис|ланы|ризе|тени)(?:\s|$|!)/i.test(name)) return false;
        return true;
    }

    function rebuildPools() {
        runtime.pools.clear();
        for (let tier = 1; tier <= 7; tier++) runtime.pools.set(tier, []);
        const sources = [$dataItems, $dataWeapons, $dataArmors];
        for (const source of sources) {
            for (const object of source || []) {
                if (!safeRewardObject(object)) continue;
                runtime.pools.get(tierOf(object)).push({
                    kind: rewardKind(object),
                    id: object.id,
                    name: object.name,
                    iconIndex: object.iconIndex || 0,
                    description: object.description || ""
                });
            }
        }
    }

    function resolvePool(tier, maximumTier) {
        for (let distance = 0; distance <= 6; distance++) {
            const down = tier - distance;
            if (down >= 1 && down <= maximumTier && (runtime.pools.get(down) || []).length) return { tier: down, pool: runtime.pools.get(down) };
            const up = tier + distance;
            if (up <= maximumTier && (runtime.pools.get(up) || []).length) return { tier: up, pool: runtime.pools.get(up) };
        }
        return { tier: 1, pool: [] };
    }

    function rewardAmount(kind, tier) {
        if (kind !== "item") return 1;
        if (tier <= 2) return 3;
        if (tier === 3) return 2;
        return 1;
    }

    function createSingleReward(tier, maximumTier, random, excluded) {
        const resolved = resolvePool(tier, maximumTier);
        let candidates = resolved.pool.filter(entry => !excluded || !excluded.has(`${entry.kind}:${entry.id}`));
        if (!candidates.length) candidates = resolved.pool;
        const entry = candidates.length ? candidates[Math.floor(random() * candidates.length)] : null;
        if (!entry) return null;
        return {
            type: "single",
            kind: entry.kind,
            id: entry.id,
            amount: rewardAmount(entry.kind, resolved.tier),
            tier: resolved.tier,
            name: entry.name,
            iconIndex: entry.iconIndex,
            description: entry.description
        };
    }

    function createChoiceReward(tiers, maximumTier, random) {
        const options = [];
        const excluded = new Set();
        for (const tier of tiers) {
            const reward = createSingleReward(tier, maximumTier, random, excluded);
            if (!reward) continue;
            options.push(reward);
            excluded.add(`${reward.kind}:${reward.id}`);
        }
        if (!options.length) return null;
        return { type: "choice", options, tier: Math.max(...options.map(option => option.tier)) };
    }

    function storedRewardIsSafe(reward) {
        if (!reward) return true;
        if (reward.type === "choice") return reward.options.every(storedRewardIsSafe);
        return safeRewardObject(databaseObject(reward));
    }

    function replaceUnsafeReward(reward, maximumTier, random) {
        if (!reward || storedRewardIsSafe(reward)) return { reward, replaced: false };
        if (reward.type === "choice") {
            const excluded = new Set();
            const options = reward.options.map(option => {
                const candidate = storedRewardIsSafe(option) ? option : createSingleReward(Math.min(option.tier, maximumTier), maximumTier, random, excluded);
                if (candidate) excluded.add(`${candidate.kind}:${candidate.id}`);
                return candidate;
            }).filter(Boolean);
            return {
                reward: options.length ? { type: "choice", options, tier: Math.max(...options.map(option => option.tier)) } : createChoiceReward([maximumTier, maximumTier, maximumTier], maximumTier, random),
                replaced: true
            };
        }
        return {
            reward: createSingleReward(Math.min(reward.tier, maximumTier), maximumTier, random),
            replaced: true
        };
    }

    function sanitizeSeasonRewards() {
        if (!$gameSystem) return;
        const active = season();
        if (!active) return;
        const random = seededRandom(hashSeed(`${active.seed}:safe-rewards-v2`));
        for (let level = 1; level <= maxLevel(); level++) {
            const free = replaceUnsafeReward(active.freeRewards[level], 6, random);
            if (free.replaced) {
                active.freeRewards[level] = free.reward;
                delete active.claimedFree[level];
            }
            const premium = replaceUnsafeReward(active.premiumRewards[level], 7, random);
            if (premium.replaced) {
                active.premiumRewards[level] = premium.reward;
                delete active.claimedPremium[level];
            }
        }
    }

    function freeTier(level, random) {
        if (level <= 4) return randomInteger(random, 1, 2);
        if (level <= 9) return randomInteger(random, 2, 3);
        if (level <= 19) return randomInteger(random, 2, 4);
        if (level <= 29) return randomInteger(random, 3, 5);
        if (level <= 39) return randomInteger(random, 4, 5);
        return 5;
    }

    function premiumTier(level, random) {
        if (level <= 9) return randomInteger(random, 2, 3);
        if (level <= 19) return randomInteger(random, 3, 4);
        if (level <= 29) return randomInteger(random, 4, 5);
        if (level <= 39) return random() < 0.2 ? 6 : 5;
        if (level < maxLevel()) return random() < 0.45 ? 6 : 5;
        return random() * 100 < numberSetting("rewards", "premiumFinalLegendaryPercent", 35) ? 7 : 6;
    }

    function generateRewardTracks(seed) {
        const random = seededRandom(seed);
        const free = {};
        const premium = {};
        const maximum = maxLevel();
        for (let level = 1; level <= maximum; level++) {
            if (level === maximum) {
                const chance = numberSetting("rewards", "freeFinalMythicPercent", 25);
                const tiers = [0, 0, 0].map(() => random() * 100 < chance ? 6 : 5);
                free[level] = createChoiceReward(tiers, 6, random);
            }
            else if ([10, 25, 40].includes(level)) {
                const tier = level === 10 ? 3 : level === 25 ? 4 : 5;
                free[level] = createChoiceReward([tier, tier, tier], 6, random);
            }
            else {
                free[level] = createSingleReward(freeTier(level, random), 6, random);
            }

            const guaranteed = level === 1 || level % 5 === 0;
            const filled = guaranteed || random() * 100 < numberSetting("premium", "intermediateRewardPercent", 70);
            if (!filled) {
                premium[level] = null;
            }
            else if ([25, 40, maximum].includes(level)) {
                const tiers = [premiumTier(level, random), premiumTier(level, random), premiumTier(level, random)];
                premium[level] = createChoiceReward(tiers, 7, random);
            }
            else {
                premium[level] = createSingleReward(premiumTier(level, random), 7, random);
            }
        }
        return { free, premium };
    }

    function challengeTemplates() {
        return [
            { id: "kills30", type: "kills", target: 30, xp: 300, title: "Охота начинается", text: "Победить 30 противников" },
            { id: "kills80", type: "kills", target: 80, xp: 500, title: "Без права на передышку", text: "Победить 80 противников" },
            { id: "bosses2", type: "bosses", target: 2, xp: 400, title: "Крупная добыча", text: "Победить 2 боссов" },
            { id: "quests3", type: "quests", target: 3, xp: 450, title: "Недельный контракт", text: "Завершить 3 задания" },
            { id: "steps2500", type: "steps", target: 2500, xp: 350, title: "Дальняя дорога", text: "Пройти 2500 шагов" },
            { id: "items40", type: "items", target: 40, xp: 300, title: "Собиратель", text: "Получить 40 предметов" },
            { id: "diliks1000", type: "diliks", target: 1000, xp: 400, title: "Удачная неделя", text: "Заработать 1000 Диликов" }
        ];
    }

    function newSeason(previousIndex) {
        const start = now();
        const duration = Math.max(1, numberSetting("season", "durationHours", 168)) * 60 * 60 * 1000;
        const index = Math.max(1, Number(previousIndex || 0) + 1);
        const seed = hashSeed(`${start}:${index}:${Math.random()}`);
        const tracks = generateRewardTracks(seed);
        return {
            index,
            seed,
            start,
            end: start + duration,
            xp: 0,
            premium: false,
            claimedFree: {},
            claimedPremium: {},
            freeRewards: tracks.free,
            premiumRewards: tracks.premium,
            counters: { kills: 0, bosses: 0, quests: 0, steps: 0, items: 0, diliks: 0 },
            sourceXp: { enemies: 0, bosses: 0, quests: 0, steps: 0, items: 0, diliks: 0 },
            sourceUnits: { steps: 0, items: 0, diliks: 0 },
            completedQuests: {},
            challenges: challengeTemplates().map(challenge => Object.assign({ complete: false }, challenge)),
            uiScrollX: 0,
            uiSelectedLevel: 1,
            uiSelectedTrack: "free"
        };
    }

    function ensureState(forceNew) {
        if (!$gameSystem) return null;
        let state = $gameSystem._dizaliaBattlePass;
        if (!state || state.version !== STATE_VERSION) {
            state = { version: STATE_VERSION, season: null };
            $gameSystem._dizaliaBattlePass = state;
        }
        if (forceNew || !state.season || now() >= Number(state.season.end || 0)) {
            state.season = newSeason(state.season && state.season.index);
        }
        return state;
    }

    function season() {
        const state = ensureState(false);
        return state && state.season;
    }

    function currentLevel(target) {
        const active = target || season();
        return active ? clamp(Math.floor(active.xp / xpPerLevel()), 0, maxLevel()) : 0;
    }

    function addXp(amount, source) {
        const active = season();
        if (!active) return 0;
        const gain = Math.max(0, Math.floor(Number(amount) || 0));
        const previous = active.xp;
        active.xp = clamp(active.xp + gain, 0, requiredXp());
        if (source && Object.prototype.hasOwnProperty.call(active.sourceXp, source)) {
            active.sourceXp[source] += active.xp - previous;
        }
        return active.xp - previous;
    }

    function addCappedXp(source, amount, cap) {
        const active = season();
        if (!active) return;
        const remaining = Math.max(0, cap - Number(active.sourceXp[source] || 0));
        addXp(Math.min(remaining, amount), source);
    }

    function checkChallenges(active) {
        for (const challenge of active.challenges) {
            if (!challenge.complete && Number(active.counters[challenge.type] || 0) >= challenge.target) {
                challenge.complete = true;
                addXp(challenge.xp);
            }
        }
    }

    function recordProgress(type, amount) {
        if (!runtime.ready || runtime.granting || !$gameSystem) return;
        const active = season();
        const gain = Math.max(0, Math.floor(Number(amount) || 0));
        if (!active || !gain) return;
        active.counters[type] = Number(active.counters[type] || 0) + gain;
        const experience = group("experience");
        if (type === "kills") addCappedXp("enemies", gain * numberSetting("experience", "enemyXp", 4), numberSetting("experience", "enemyWeeklyCap", 600));
        if (type === "bosses") addCappedXp("bosses", gain * numberSetting("experience", "bossXp", 80), numberSetting("experience", "bossWeeklyCap", 400));
        if (type === "quests") addCappedXp("quests", gain * numberSetting("experience", "questXp", 120), numberSetting("experience", "questWeeklyCap", 600));
        const batchData = {
            steps: [numberSetting("experience", "stepsPerGrant", 100), numberSetting("experience", "stepXp", 10), numberSetting("experience", "stepWeeklyCap", 300)],
            items: [numberSetting("experience", "itemsPerGrant", 5), numberSetting("experience", "itemXp", 10), numberSetting("experience", "itemWeeklyCap", 300)],
            diliks: [numberSetting("experience", "diliksPerGrant", 100), numberSetting("experience", "dilikXp", 15), numberSetting("experience", "dilikWeeklyCap", 300)]
        };
        if (batchData[type]) {
            const [batchSize, batchXp, cap] = batchData[type];
            const oldUnits = Number(active.sourceUnits[type] || 0);
            const newUnits = Math.floor(active.counters[type] / Math.max(1, batchSize));
            if (newUnits > oldUnits) {
                active.sourceUnits[type] = newUnits;
                addCappedXp(type, (newUnits - oldUnits) * batchXp, cap);
            }
        }
        checkChallenges(active);
    }

    function isBossEnemy(enemy) {
        if (!enemy) return false;
        if (typeof enemy.isBoss === "function" && enemy.isBoss()) return true;
        const data = enemy.enemy && enemy.enemy();
        const note = String(data && data.note || "");
        const name = String(data && data.name || "");
        return /<(?:Boss|UnhingedBoss)>/i.test(note) || /Нортум|Югель|Древний Лич|Королевская слизь|Марипос/i.test(name);
    }

    function rewardDescription(reward) {
        if (!reward) return "На этом уровне Premium-награды нет.";
        if (reward.type === "choice") return `Выберите один из ${reward.options.length} предметов.`;
        const object = databaseObject(reward);
        return String(object && object.description || reward.description || "Описание отсутствует.");
    }

    function grantReward(reward) {
        if (!reward || reward.type !== "single") return false;
        const object = databaseObject(reward);
        if (!object || !$gameParty) return false;
        runtime.granting = true;
        try {
            $gameParty.gainItem(object, Math.max(1, reward.amount || 1));
        }
        finally {
            runtime.granting = false;
        }
        return true;
    }

    function rewardAt(track, level) {
        const active = season();
        if (!active) return null;
        return track === "premium" ? active.premiumRewards[level] : active.freeRewards[level];
    }

    function isClaimed(track, level) {
        const active = season();
        if (!active) return false;
        return !!(track === "premium" ? active.claimedPremium[level] : active.claimedFree[level]);
    }

    function canClaim(track, level) {
        const active = season();
        if (!active || level > currentLevel(active) || isClaimed(track, level)) return false;
        if (track === "premium" && !active.premium) return false;
        return !!rewardAt(track, level);
    }

    function claimableRewardCount() {
        const active = season();
        if (!active) return 0;
        let count = 0;
        for (const track of ["free", "premium"]) {
            if (track === "premium" && !active.premium) continue;
            for (let level = 1; level <= currentLevel(active); level++) {
                if (canClaim(track, level)) count++;
            }
        }
        return count;
    }

    function markClaimed(track, level) {
        const active = season();
        if (track === "premium") active.claimedPremium[level] = true;
        else active.claimedFree[level] = true;
    }

    function claimReward(track, level, optionIndex) {
        if (!canClaim(track, level)) return { ok: false, reason: "Награда пока недоступна." };
        const reward = rewardAt(track, level);
        const selected = reward.type === "choice" ? reward.options[Number(optionIndex)] : reward;
        if (!selected) return { ok: false, reason: "Сначала выберите награду." };
        if (!grantReward(selected)) return { ok: false, reason: "Предмет больше не существует в базе." };
        markClaimed(track, level);
        return { ok: true, reward: selected };
    }

    function claimAllSingles() {
        const active = season();
        let count = 0;
        for (const track of ["free", "premium"]) {
            if (track === "premium" && !active.premium) continue;
            for (let level = 1; level <= currentLevel(active); level++) {
                const reward = rewardAt(track, level);
                if (reward && reward.type === "single" && claimReward(track, level).ok) count++;
            }
        }
        return count;
    }

    function buyPremium() {
        const active = season();
        const price = Math.max(0, Math.floor(numberSetting("premium", "priceDiliks", 1000)));
        if (active.premium) return { ok: true, already: true };
        if (!$gameParty || $gameParty.gold() < price) return { ok: false, price };
        runtime.granting = true;
        try {
            $gameParty.loseGold(price);
        }
        finally {
            runtime.granting = false;
        }
        active.premium = true;
        return { ok: true, price };
    }

    function buyLevels(amount) {
        const active = season();
        const available = maxLevel() - currentLevel(active);
        const levels = clamp(Math.floor(Number(amount) || 0), 0, available);
        const priceEach = Math.max(0, Math.floor(numberSetting("premium", "levelPriceDiliks", 100)));
        const price = levels * priceEach;
        if (!levels) return { ok: false, reason: "Достигнут максимальный уровень." };
        if (!$gameParty || $gameParty.gold() < price) return { ok: false, reason: `Недостаточно Диликов. Нужно: ${price}.`, price };
        runtime.granting = true;
        try {
            $gameParty.loseGold(price);
            addXp(levels * xpPerLevel());
        }
        finally {
            runtime.granting = false;
        }
        return { ok: true, levels, price };
    }

    function timeRemainingText() {
        const active = season();
        if (!active) return "";
        let seconds = Math.max(0, Math.floor((active.end - now()) / 1000));
        const days = Math.floor(seconds / 86400);
        seconds %= 86400;
        const hours = Math.floor(seconds / 3600);
        seconds %= 3600;
        const minutes = Math.floor(seconds / 60);
        return `${days} д. ${String(hours).padStart(2, "0")} ч. ${String(minutes).padStart(2, "0")} мин.`;
    }

    function itemIconBitmap(iconIndex) {
        const bitmap = ImageManager.loadSystem("IconSet");
        const width = ImageManager.iconWidth;
        const height = ImageManager.iconHeight;
        const sourceX = iconIndex % 16 * width;
        const sourceY = Math.floor(iconIndex / 16) * height;
        return { bitmap, sourceX, sourceY, width, height };
    }

    function drawIsolatedIcon(windowObject, iconIndex, x, y, size) {
        const icon = itemIconBitmap(iconIndex || 0);
        const targetSize = size || ImageManager.iconWidth;
        windowObject.contents.blt(icon.bitmap, icon.sourceX, icon.sourceY, icon.width, icon.height, x, y, targetSize, targetSize);
    }

    function installQuestHooks() {
        if (runtime.questHooksInstalled) return;
        if (window.SQSM && typeof SQSM.CompleteQuest === "function") {
            const original = SQSM.CompleteQuest;
            SQSM.CompleteQuest = function(questId) {
                const result = original.apply(this, arguments);
                recordQuestOnce(`pkd:${questId}`);
                return result;
            };
        }
        if (window.SashaQS && typeof SashaQS.completeBoardQuest === "function") {
            const original = SashaQS.completeBoardQuest;
            SashaQS.completeBoardQuest = function(questId) {
                const result = original.apply(this, arguments);
                recordQuestOnce(`sasha:${questId}`);
                return result;
            };
        }
        runtime.questHooksInstalled = true;
    }

    function recordQuestOnce(key) {
        const active = season();
        if (!active || active.completedQuests[key]) return;
        active.completedQuests[key] = true;
        recordProgress("quests", 1);
    }

    function setInterfaceFont(windowObject, size) {
        windowObject.contents.fontSize = size;
        windowObject.contents.outlineWidth = 3;
    }

    function drawFittedText(windowObject, text, x, y, width, align, maximumSize, minimumSize) {
        const previousSize = windowObject.contents.fontSize;
        const maximum = maximumSize || previousSize || 20;
        const minimum = minimumSize || 14;
        windowObject.contents.fontSize = maximum;
        while (windowObject.contents.fontSize > minimum && windowObject.textWidth(String(text)) > width) {
            windowObject.contents.fontSize--;
        }
        windowObject.drawText(String(text), x, y, width, align || "left");
        windowObject.contents.fontSize = previousSize;
    }

    function hideWindowCursor(windowObject) {
        windowObject.cursorVisible = false;
        if (windowObject._cursorSprite) windowObject._cursorSprite.visible = false;
    }

    class Window_DBPHeader extends Window_Base {
        initialize(rect) {
            super.initialize(rect);
            this.opacity = 0;
            this._lastMinute = -1;
            this.refresh();
        }

        update() {
            super.update();
            const minute = Math.floor(now() / 60000);
            if (minute !== this._lastMinute) {
                this._lastMinute = minute;
                this.refresh();
            }
        }

        refresh() {
            this.contents.clear();
            const active = season();
            if (!active) return;
            const level = currentLevel(active);
            const next = level >= maxLevel() ? requiredXp() : (level + 1) * xpPerLevel();
            setInterfaceFont(this, 22);
            this.changeTextColor(ColorManager.systemColor());
            this.drawText("БОЕВОЙ ПРОПУСК", 0, -2, 300, "left");
            this.resetTextColor();
            this.drawText(`Уровень ${level}/${maxLevel()}  ·  XP ${active.xp}/${next}`, 310, -2, 390, "center");
            this.changeTextColor(active.premium ? TIER_COLORS[7] : ColorManager.normalColor());
            this.drawText(active.premium ? "PREMIUM АКТИВЕН" : "БЕСПЛАТНАЯ ЛИНИЯ", 720, -2, 360, "right");
            this.resetTextColor();
            setInterfaceFont(this, 17);
            this.changeTextColor(TIER_COLORS[2]);
            this.drawText(`До сброса: ${timeRemainingText()}`, 720, 31, 360, "right");
            this.resetTextColor();
        }
    }

    class Window_DBPTrack extends Window_Base {
        initialize(rect) {
            super.initialize(rect);
            this.opacity = 0;
            this._columnWidth = 184;
            const active = season();
            const legacyScroll = Number(active.uiPage || 0) * this._columnWidth * 5;
            this._scrollX = clamp(Number(active.uiScrollX ?? legacyScroll), 0, this.maxScrollX());
            this._targetScrollX = this._scrollX;
            this._selectedLevel = clamp(Number(active.uiSelectedLevel || Math.max(1, currentLevel(active) + 1)), 1, maxLevel());
            this._selectedTrack = active.uiSelectedTrack === "premium" && rewardAt("premium", this._selectedLevel) ? "premium" : "free";
            this._armedSelection = false;
            this._dragging = false;
            this._dragMoved = false;
            this._interactionEnabled = true;
            this._claimHandler = null;
            this._detailsWindow = null;
            this.refresh();
        }

        maxScrollX() {
            return Math.max(0, maxLevel() * this._columnWidth - this.innerWidth);
        }

        setDetailsWindow(windowObject) {
            this._detailsWindow = windowObject;
            this.updateDetails();
        }

        setClaimHandler(handler) {
            this._claimHandler = handler;
        }

        setInteractionEnabled(enabled) {
            this._interactionEnabled = !!enabled;
            if (!enabled) this._dragging = false;
        }

        currentData() {
            return {
                level: this._selectedLevel,
                track: this._selectedTrack,
                reward: rewardAt(this._selectedTrack, this._selectedLevel)
            };
        }

        updateDetails() {
            if (this._detailsWindow) this._detailsWindow.setRewardData(this.currentData());
        }

        selectReward(level, track) {
            const reward = rewardAt(track, level);
            if (!reward) return;
            const same = level === this._selectedLevel && track === this._selectedTrack;
            this._selectedLevel = level;
            this._selectedTrack = track;
            const active = season();
            active.uiSelectedLevel = level;
            active.uiSelectedTrack = track;
            this.refresh();
            this.updateDetails();
            if (same && this._armedSelection && this._claimHandler) this._claimHandler(this.currentData());
            this._armedSelection = true;
        }

        ensureSelectionVisible() {
            const left = (this._selectedLevel - 1) * this._columnWidth;
            const right = left + this._columnWidth;
            if (left < this._targetScrollX) this._targetScrollX = left;
            if (right > this._targetScrollX + this.innerWidth) this._targetScrollX = right - this.innerWidth;
            this._targetScrollX = clamp(this._targetScrollX, 0, this.maxScrollX());
        }

        moveSelection(offset) {
            const level = clamp(this._selectedLevel + offset, 1, maxLevel());
            this._selectedLevel = level;
            if (!rewardAt(this._selectedTrack, level)) this._selectedTrack = "free";
            this._armedSelection = false;
            this.ensureSelectionVisible();
            this.updateDetails();
            this.refresh();
        }

        localPointer() {
            return {
                x: TouchInput.x - this.x - this.padding,
                y: TouchInput.y - this.y - this.padding
            };
        }

        pointerInside() {
            const point = this.localPointer();
            return point.x >= 0 && point.y >= 0 && point.x < this.innerWidth && point.y < this.innerHeight;
        }

        update() {
            super.update();
            if (!this._interactionEnabled) return;
            this.updateWheel();
            this.updateDrag();
            this.updateKeyboard();
            if (!this._dragging && Math.abs(this._targetScrollX - this._scrollX) > 0.25) {
                this._scrollX += (this._targetScrollX - this._scrollX) * 0.28;
                if (Math.abs(this._targetScrollX - this._scrollX) < 0.5) this._scrollX = this._targetScrollX;
                season().uiScrollX = this._scrollX;
                this.refresh();
            }
        }

        updateWheel() {
            if (!this.pointerInside()) return;
            const wheel = Math.abs(TouchInput.wheelX || 0) > Math.abs(TouchInput.wheelY || 0) ? TouchInput.wheelX : TouchInput.wheelY;
            if (!wheel) return;
            this._targetScrollX = clamp(this._targetScrollX + wheel * 1.15, 0, this.maxScrollX());
            this._armedSelection = false;
        }

        updateDrag() {
            if (TouchInput.isTriggered() && this.pointerInside()) {
                this._dragging = true;
                this._dragMoved = false;
                this._dragStartX = TouchInput.x;
                this._dragStartScrollX = this._scrollX;
            }
            if (this._dragging && TouchInput.isPressed()) {
                const distance = TouchInput.x - this._dragStartX;
                if (Math.abs(distance) > 6) this._dragMoved = true;
                if (this._dragMoved) {
                    this._scrollX = clamp(this._dragStartScrollX - distance, 0, this.maxScrollX());
                    this._targetScrollX = this._scrollX;
                    season().uiScrollX = this._scrollX;
                    this._armedSelection = false;
                    this.refresh();
                }
            }
            if (this._dragging && TouchInput.isReleased()) {
                if (this._dragMoved) {
                    this._targetScrollX = clamp(Math.round(this._scrollX / this._columnWidth) * this._columnWidth, 0, this.maxScrollX());
                }
                else {
                    this.processPointerClick();
                }
                this._dragging = false;
            }
        }

        updateKeyboard() {
            if (!this.pointerInside()) return;
            if (Input.isRepeated("left")) this.moveSelection(-1);
            if (Input.isRepeated("right")) this.moveSelection(1);
        }

        processPointerClick() {
            const point = this.localPointer();
            const level = Math.floor((point.x + this._scrollX) / this._columnWidth) + 1;
            if (level < 1 || level > maxLevel()) return;
            const track = point.y < this.innerHeight / 2 ? "free" : "premium";
            this.selectReward(level, track);
        }

        refresh() {
            if (!this.contents) return;
            this.contents.clear();
            setInterfaceFont(this, 18);
            const firstLevel = Math.max(1, Math.floor(this._scrollX / this._columnWidth) + 1);
            const lastLevel = Math.min(maxLevel(), firstLevel + Math.ceil(this.innerWidth / this._columnWidth) + 1);
            for (let level = firstLevel; level <= lastLevel; level++) {
                const x = (level - 1) * this._columnWidth - this._scrollX + 7;
                this.drawRewardCard(level, "free", x, 4);
                this.drawRewardCard(level, "premium", x, Math.floor(this.innerHeight / 2) + 3);
            }
            const progressWidth = Math.max(50, this.innerWidth * this.innerWidth / (maxLevel() * this._columnWidth));
            const progressX = this.maxScrollX() > 0 ? (this.innerWidth - progressWidth) * this._scrollX / this.maxScrollX() : 0;
            this.contents.fillRect(0, this.innerHeight - 4, this.innerWidth, 2, "rgba(113,225,229,0.16)");
            this.contents.fillRect(progressX, this.innerHeight - 5, progressWidth, 3, TIER_COLORS[2]);
        }

        drawRewardCard(level, track, x, y) {
            const reward = rewardAt(track, level);
            if (!reward) return;
            const width = this._columnWidth - 14;
            const height = Math.floor(this.innerHeight / 2) - 11;
            const selected = level === this._selectedLevel && track === this._selectedTrack;
            const unlocked = level <= currentLevel();
            const claimed = isClaimed(track, level);
            const premiumLocked = track === "premium" && !season().premium;
            const display = reward.type === "choice" ? reward.options[0] : reward;
            this.contents.fillRect(x, y, width, height, track === "premium" ? "rgba(47,29,66,0.90)" : "rgba(10,34,47,0.90)");
            this.contents.fillRect(x, y, width, 3, TIER_COLORS[reward.tier]);
            if (selected) {
                this.contents.fillRect(x, y, width, 2, "#ffffff");
                this.contents.fillRect(x, y + height - 2, width, 2, "#ffffff");
                this.contents.fillRect(x, y, 2, height, "#ffffff");
                this.contents.fillRect(x + width - 2, y, 2, height, "#ffffff");
            }
            this.changeTextColor(track === "premium" ? TIER_COLORS[7] : TIER_COLORS[2]);
            drawFittedText(this, `${TRACK_LABELS[track]} · ${level}`, x + 8, y + 5, width - 16, "center", 18, 14);
            drawIsolatedIcon(this, display.iconIndex, x + Math.floor((width - 32) / 2), y + 40, 32);
            if (display.amount > 1 && reward.type !== "choice") {
                this.resetTextColor();
                this.drawText(`×${display.amount}`, x + width - 42, y + 41, 34, "right");
            }
            this.changeTextColor(TIER_COLORS[reward.tier]);
            drawFittedText(this, reward.type === "choice" ? "ВЫБОР ИЗ 3" : display.name, x + 8, y + 78, width - 16, "center", 19, 13);
            this.resetTextColor();
            setInterfaceFont(this, 15);
            if (claimed) {
                this.changeTextColor(TIER_COLORS[3]);
                this.drawText("✓ ПОЛУЧЕНО", x + 8, y + height - 30, width - 16, "center");
            }
            else if (premiumLocked) {
                this.changeTextColor(TIER_COLORS[7]);
                this.drawText("◆", x + width - 28, y + height - 30, 18, "center");
            }
            else if (!unlocked) {
                this.changePaintOpacity(false);
                this.drawText(`ур. ${level}`, x + 8, y + height - 30, width - 16, "center");
                this.changePaintOpacity(true);
            }
            else if (selected) {
                this.changeTextColor(TIER_COLORS[2]);
                drawFittedText(this, "ЕЩЁ КЛИК — ЗАБРАТЬ", x + 5, y + height - 30, width - 10, "center", 14, 10);
            }
            this.resetTextColor();
        }
    }

    class Window_DBPDetails extends Window_Base {
        initialize(rect) {
            super.initialize(rect);
            this.opacity = 0;
            this._data = null;
        }

        setRewardData(data) {
            if (!data) return;
            this._data = data;
            this.refresh();
        }

        refresh() {
            this.contents.clear();
            const data = this._data;
            if (!data) return;
            const reward = data.reward;
            setInterfaceFont(this, 18);
            this.changeTextColor(ColorManager.systemColor());
            this.drawText(`${TRACK_LABELS[data.track]} · УРОВЕНЬ ${data.level}`, 0, 0, 300, "left");
            this.resetTextColor();
            if (!reward) {
                this.drawText("На этой позиции награды нет.", 0, 42, this.innerWidth, "left");
                return;
            }
            let status = data.level > currentLevel() ? `Откроется на уровне ${data.level}` : isClaimed(data.track, data.level) ? "Уже получено" : data.track === "premium" && !season().premium ? "Требуется Premium" : "Кликните по карточке ещё раз, чтобы получить";
            this.changeTextColor(isClaimed(data.track, data.level) ? TIER_COLORS[3] : TIER_COLORS[2]);
            drawFittedText(this, status, 315, 0, this.innerWidth - 315, "right", 17, 13);
            this.resetTextColor();
            if (reward.type === "choice") {
                this.changeTextColor(TIER_COLORS[reward.tier]);
                this.drawText("НАГРАДА НА ВЫБОР", 0, 32, 300, "left");
                this.resetTextColor();
                let x = 0;
                for (const option of reward.options) {
                    drawIsolatedIcon(this, option.iconIndex, x, 72, 32);
                    this.changeTextColor(TIER_COLORS[option.tier]);
                    drawFittedText(this, option.name, x + 40, 68, Math.floor(this.innerWidth / 3) - 48, "left", 17, 13);
                    this.resetTextColor();
                    x += Math.floor(this.innerWidth / 3);
                }
                setInterfaceFont(this, 15);
                this.drawText("После получения выбор фиксируется до конца недели.", 0, 112, this.innerWidth, "left");
                return;
            }
            drawIsolatedIcon(this, reward.iconIndex, 0, 45, 32);
            this.changeTextColor(TIER_COLORS[reward.tier]);
            drawFittedText(this, `${reward.name}${reward.amount > 1 ? ` ×${reward.amount}` : ""}`, 44, 39, this.innerWidth - 44, "left", 20, 14);
            this.resetTextColor();
            setInterfaceFont(this, 15);
            this.drawTextEx(rewardDescription(reward), 44, 78, this.innerWidth - 44);
        }
    }

    class Window_DBPCommands extends Window_Command {
        initialize(rect) {
            super.initialize(rect);
            this.opacity = 0;
            hideWindowCursor(this);
        }

        makeCommandList() {
            const active = season();
            this.addCommand("Недельные задачи", "challenges", true);
            this.addCommand("Забрать доступное", "claimAll", currentLevel(active) > 0);
            this.addCommand("Купить уровни", "buyLevels", currentLevel(active) < maxLevel());
            this.addCommand(active.premium ? "Premium активен" : `Premium ${setting("premium", "displayPrice", "$9.99")}`, "premium", !active.premium);
            this.addCommand("Закрыть", "cancel", true);
        }

        itemHeight() {
            return 68;
        }

        _refreshCursor() {
            hideWindowCursor(this);
        }

        _updateCursor() {
            hideWindowCursor(this);
        }

        select(index) {
            super.select(index);
            if (this.contents) this.refresh();
        }

        drawItem(index) {
            const rect = this.itemLineRect(index);
            const selected = index === this.index();
            if (selected) {
                this.contents.fillRect(rect.x, rect.y + 3, rect.width, rect.height - 6, "rgba(72,202,208,0.17)");
                this.contents.fillRect(rect.x, rect.y + 3, 3, rect.height - 6, TIER_COLORS[2]);
            }
            this.changePaintOpacity(this.isCommandEnabled(index));
            setInterfaceFont(this, 18);
            drawFittedText(this, this.commandName(index), rect.x + 12, rect.y, rect.width - 24, "left", 18, 14);
            this.changePaintOpacity(true);
        }
    }

    class Window_DBPChallenges extends Window_Base {
        initialize(rect) {
            super.initialize(rect);
            this.opacity = 0;
            this.refresh();
        }

        refresh() {
            this.contents.clear();
            const active = season();
            if (!active) return;
            const rowHeight = 62;
            setInterfaceFont(this, 18);
            for (let index = 0; index < active.challenges.length; index++) {
                const challenge = active.challenges[index];
                const y = index * rowHeight;
                const progress = Math.min(challenge.target, Number(active.counters[challenge.type] || 0));
                const ratio = challenge.target > 0 ? progress / challenge.target : 0;
                this.contents.fillRect(0, y + 3, this.innerWidth, rowHeight - 8, challenge.complete ? "rgba(40,112,76,0.25)" : "rgba(6,16,28,0.64)");
                this.contents.fillRect(0, y + rowHeight - 8, this.innerWidth, 3, "rgba(90,220,220,0.12)");
                this.contents.fillRect(0, y + rowHeight - 8, Math.floor(this.innerWidth * ratio), 3, challenge.complete ? TIER_COLORS[3] : TIER_COLORS[2]);
                this.changeTextColor(challenge.complete ? TIER_COLORS[3] : ColorManager.normalColor());
                drawFittedText(this, challenge.title, 18, y + 5, 270, "left", 18, 14);
                this.resetTextColor();
                drawFittedText(this, challenge.text, 310, y + 5, 385, "left", 18, 14);
                this.drawText(`${progress}/${challenge.target}`, 710, y + 5, 180, "right");
                this.changeTextColor(TIER_COLORS[2]);
                this.drawText(`+${challenge.xp} XP`, 910, y + 5, this.innerWidth - 930, "right");
                this.resetTextColor();
            }
        }
    }

    class Window_DBPChoice extends Window_Command {
        initialize(rect) {
            this._reward = null;
            super.initialize(rect);
            this.opacity = 0;
            this.openness = 0;
            this.hide();
            this.deactivate();
            hideWindowCursor(this);
        }

        setReward(reward) {
            this._reward = reward;
            this.refresh();
            this.select(0);
        }

        makeCommandList() {
            for (let index = 0; index < (this._reward ? this._reward.options.length : 0); index++) {
                const option = this._reward.options[index];
                this.addCommand(`${option.name}${option.amount > 1 ? ` ×${option.amount}` : ""} · ${TIER_NAMES[option.tier]}`, "choice", true, index);
            }
            this.addCommand("Отмена", "cancel", true, -1);
        }

        _refreshCursor() {
            hideWindowCursor(this);
        }

        _updateCursor() {
            hideWindowCursor(this);
        }

        drawItem(index) {
            const rect = this.itemLineRect(index);
            this.contents.fillRect(rect.x, rect.y + 2, rect.width, rect.height - 4, index === this.index() ? "rgba(86,210,214,0.28)" : "rgba(5,13,24,0.88)");
            const optionIndex = this._list[index] && this._list[index].ext;
            const option = optionIndex >= 0 && this._reward ? this._reward.options[optionIndex] : null;
            setInterfaceFont(this, 18);
            if (option) {
                drawIsolatedIcon(this, option.iconIndex, rect.x, rect.y + 2, 30);
                this.changeTextColor(TIER_COLORS[option.tier]);
                drawFittedText(this, this.commandName(index), rect.x + 42, rect.y, rect.width - 50, "left", 18, 14);
                this.resetTextColor();
            }
            else {
                this.drawText(this.commandName(index), rect.x + 12, rect.y, rect.width - 24, "center");
            }
        }
    }

    class Window_DBPLevelPurchaseInfo extends Window_Base {
        initialize(rect) {
            super.initialize(rect);
            this.opacity = 0;
            this.openness = 0;
            this._amount = 1;
            this.hide();
        }

        setAmount(amount) {
            this._amount = Math.max(0, Number(amount) || 0);
            this.refresh();
        }

        refresh() {
            this.contents.clear();
            this.contents.fillRect(0, 0, this.innerWidth, this.innerHeight, "rgba(4,12,23,0.96)");
            this.contents.fillRect(0, 0, this.innerWidth, 3, TIER_COLORS[2]);
            setInterfaceFont(this, 21);
            this.changeTextColor(ColorManager.systemColor());
            this.drawText("ПОКУПКА УРОВНЕЙ", 18, 10, this.innerWidth - 36, "center");
            this.resetTextColor();
            const price = this._amount * numberSetting("premium", "levelPriceDiliks", 100);
            const targetLevel = clamp(currentLevel() + this._amount, 0, maxLevel());
            setInterfaceFont(this, 18);
            this.drawText(`Выбрано: ${this._amount} ур.`, 24, 55, 210, "left");
            this.drawText(`Новый уровень: ${targetLevel}/${maxLevel()}`, 250, 55, this.innerWidth - 274, "right");
            this.changeTextColor(TIER_COLORS[7]);
            this.drawText(`ИТОГО: ${price} Диликов`, 24, 92, this.innerWidth - 48, "center");
            this.resetTextColor();
        }
    }

    class Window_DBPLevelPurchase extends Window_Command {
        initialize(rect) {
            this._infoWindow = null;
            super.initialize(rect);
            this.opacity = 0;
            this.openness = 0;
            this.hide();
            this.deactivate();
            hideWindowCursor(this);
        }

        setInfoWindow(windowObject) {
            this._infoWindow = windowObject;
            this.updateInfoWindow();
        }

        updateInfoWindow() {
            if (this._infoWindow) this._infoWindow.setAmount(this.currentExt() || 0);
        }

        select(index) {
            super.select(index);
            this.updateInfoWindow();
            if (this.contents) this.refresh();
        }

        makeCommandList() {
            const remaining = maxLevel() - currentLevel();
            const values = [...new Set([1, 5, 10, remaining].filter(value => value > 0 && value <= remaining))];
            for (const value of values) this.addCommand(value === remaining && ![1, 5, 10].includes(value) ? `До максимума · ${value} ур.` : `${value} ${value === 1 ? "уровень" : "уровней"}`, "levels", true, value);
            this.addCommand("Отмена", "cancel", true, 0);
        }

        _refreshCursor() {
            hideWindowCursor(this);
        }

        _updateCursor() {
            hideWindowCursor(this);
        }

        drawItem(index) {
            const rect = this.itemLineRect(index);
            this.contents.fillRect(rect.x, rect.y + 2, rect.width, rect.height - 4, index === this.index() ? "rgba(86,210,214,0.28)" : "rgba(5,13,24,0.88)");
            setInterfaceFont(this, 18);
            this.drawText(this.commandName(index), rect.x + 12, rect.y, rect.width - 24, "center");
        }
    }

    class Window_DBPNotice extends Window_Base {
        initialize(rect) {
            super.initialize(rect);
            this.opacity = 0;
            this.openness = 0;
            this._baseY = rect.y;
            this._backgroundBitmap = Alloy.Resources.loadImage(MOD_NAME, "DBattlePass_Reward", { path: ["assets"], prefix: ".png" });
            this._backgroundBitmap.addLoadListener(() => {
                if (this.visible) this.refresh();
            });
            this._display = null;
            this.hide();
        }

        drawBackground() {
            if (this._backgroundBitmap && this._backgroundBitmap.isReady()) {
                this.contents.blt(this._backgroundBitmap, 0, 0, this._backgroundBitmap.width, this._backgroundBitmap.height, 0, 0, this.innerWidth, this.innerHeight);
            }
            else {
                this.contents.fillRect(0, 0, this.innerWidth, this.innerHeight, "rgba(4,11,20,0.96)");
            }
        }

        showReward(reward, title) {
            this._display = { type: "reward", reward, title: title || "НАГРАДА ПОЛУЧЕНА" };
            this.startDisplay();
        }

        showText(text, color) {
            this._display = { type: "text", text: String(text), color: color || ColorManager.normalColor() };
            this.startDisplay();
        }

        startDisplay() {
            this.refresh();
            this.y = this._baseY + 20;
            this.contentsOpacity = 0;
            this.show();
            this.open();
            this._frames = 180;
        }

        refresh() {
            if (!this.contents) return;
            this.contents.clear();
            this.drawBackground();
            if (!this._display) return;
            if (this._display.type === "reward") {
                const reward = this._display.reward;
                setInterfaceFont(this, 17);
                this.changeTextColor(ColorManager.systemColor());
                this.drawText(this._display.title, 22, 8, this.innerWidth - 44, "center");
                this.resetTextColor();
                drawIsolatedIcon(this, reward.iconIndex, 28, 61, 32);
                this.changeTextColor(TIER_COLORS[reward.tier]);
                drawFittedText(this, reward.name, 78, 49, this.innerWidth - 160, "left", 23, 15);
                setInterfaceFont(this, 15);
                this.drawText(TIER_NAMES[reward.tier], 78, 84, 220, "left");
                this.resetTextColor();
                if (reward.amount > 1) {
                    setInterfaceFont(this, 24);
                    this.drawText(`×${reward.amount}`, this.innerWidth - 92, 57, 64, "right");
                }
                setInterfaceFont(this, 14);
                this.changeTextColor(TIER_COLORS[2]);
                this.drawText("Предмет добавлен в инвентарь", 22, 119, this.innerWidth - 44, "center");
                this.resetTextColor();
            }
            else {
                setInterfaceFont(this, 16);
                this.changeTextColor(ColorManager.systemColor());
                this.drawText("УВЕДОМЛЕНИЕ", 22, 8, this.innerWidth - 44, "center");
                this.changeTextColor(this._display.color);
                drawFittedText(this, this._display.text, 28, 57, this.innerWidth - 56, "center", 18, 11);
                this.resetTextColor();
            }
        }

        update() {
            super.update();
            if (this._frames > 0) {
                this._frames--;
                this.y += (this._baseY - this.y) * 0.24;
                this.contentsOpacity = Math.min(255, this.contentsOpacity + 34);
                if (this._frames < 28) this.contentsOpacity = Math.max(0, Math.floor(255 * this._frames / 28));
                if (this._frames === 0) {
                    this.close();
                    this.hide();
                }
            }
        }
    }

    class Scene_DizaliaBattlePass extends Scene_MenuBase {
        createBackground() {
            this._backgroundSprite = new Sprite(Alloy.Resources.loadImage(MOD_NAME, "DBattlePass_Main", { path: ["assets"], prefix: ".png" }));
            this.addChild(this._backgroundSprite);
        }

        create() {
            super.create();
            ensureState(false);
            sanitizeSeasonRewards();
            this.createHeaderWindow();
            this.createDetailsWindow();
            this.createTrackWindow();
            this.createCommandWindow();
            this.createOverlayWindows();
        }

        start() {
            super.start();
            installQuestHooks();
            this.refreshAll();
            this._commandWindow.activate();
        }

        createHeaderWindow() {
            this._headerWindow = new Window_DBPHeader(new Rectangle(32, 10, Graphics.boxWidth - 170, 82));
            this.addWindow(this._headerWindow);
        }

        createDetailsWindow() {
            this._detailsWindow = new Window_DBPDetails(new Rectangle(282, 506, 964, 183));
            this.addWindow(this._detailsWindow);
        }

        createTrackWindow() {
            this._trackWindow = new Window_DBPTrack(new Rectangle(282, 105, 964, 383));
            this._trackWindow.setDetailsWindow(this._detailsWindow);
            this._trackWindow.setClaimHandler(this.onTrackOk.bind(this));
            this.addWindow(this._trackWindow);
        }

        createCommandWindow() {
            this._commandWindow = new Window_DBPCommands(new Rectangle(32, 105, 230, 584));
            this._commandWindow.setHandler("challenges", this.showChallenges.bind(this));
            this._commandWindow.setHandler("claimAll", this.claimAll.bind(this));
            this._commandWindow.setHandler("buyLevels", this.showLevelPurchase.bind(this));
            this._commandWindow.setHandler("premium", () => SceneManager.push(Scene_DizaliaBattlePassPayment));
            this._commandWindow.setHandler("cancel", this.popScene.bind(this));
            this.addWindow(this._commandWindow);
        }

        createOverlayWindows() {
            this._choiceWindow = new Window_DBPChoice(new Rectangle(310, 205, 660, 280));
            this._choiceWindow.setHandler("choice", this.onChoice.bind(this));
            this._choiceWindow.setHandler("cancel", this.hideChoice.bind(this));
            this.addWindow(this._choiceWindow);
            this._levelInfoWindow = new Window_DBPLevelPurchaseInfo(new Rectangle(370, 145, 540, 160));
            this.addWindow(this._levelInfoWindow);
            this._levelWindow = new Window_DBPLevelPurchase(new Rectangle(370, 315, 540, 260));
            this._levelWindow.setInfoWindow(this._levelInfoWindow);
            this._levelWindow.setHandler("levels", this.onBuyLevels.bind(this));
            this._levelWindow.setHandler("cancel", this.hideLevelPurchase.bind(this));
            this.addWindow(this._levelWindow);
            this._noticeWindow = new Window_DBPNotice(new Rectangle(390, 245, 500, 180));
            this.addWindow(this._noticeWindow);
        }

        onTrackOk(data) {
            if (!canClaim(data.track, data.level)) {
                SoundManager.playBuzzer();
                return;
            }
            if (data.reward.type === "choice") {
                this._pendingChoice = data;
                this._trackWindow.setInteractionEnabled(false);
                this._choiceWindow.setReward(data.reward);
                this._choiceWindow.show();
                this._choiceWindow.open();
                this._choiceWindow.activate();
                return;
            }
            const result = claimReward(data.track, data.level);
            if (result.ok) {
                SoundManager.playOk();
                this._noticeWindow.showReward(result.reward, "НАГРАДА ПОЛУЧЕНА");
            }
            else SoundManager.playBuzzer();
            this.refreshAll();
        }

        onChoice() {
            const optionIndex = this._choiceWindow.currentExt();
            const data = this._pendingChoice;
            const result = claimReward(data.track, data.level, optionIndex);
            this.hideChoice();
            if (result.ok) {
                SoundManager.playOk();
                this._noticeWindow.showReward(result.reward, "ВЫБОР ПОДТВЕРЖДЁН");
            }
            else SoundManager.playBuzzer();
            this.refreshAll();
        }

        hideChoice() {
            this._choiceWindow.close();
            this._choiceWindow.hide();
            this._choiceWindow.deactivate();
            this._pendingChoice = null;
            this._trackWindow.setInteractionEnabled(true);
        }

        showChallenges() {
            SceneManager.push(Scene_DizaliaBattlePassChallenges);
        }

        showLevelPurchase() {
            this._trackWindow.setInteractionEnabled(false);
            this._levelWindow.refresh();
            this._levelInfoWindow.show();
            this._levelInfoWindow.open();
            this._levelWindow.show();
            this._levelWindow.open();
            this._levelWindow.activate();
            this._levelWindow.select(0);
        }

        hideLevelPurchase() {
            this._levelInfoWindow.close();
            this._levelInfoWindow.hide();
            this._levelWindow.close();
            this._levelWindow.hide();
            this._levelWindow.deactivate();
            this._trackWindow.setInteractionEnabled(true);
            this._commandWindow.activate();
        }

        onBuyLevels() {
            const result = buyLevels(this._levelWindow.currentExt());
            this.hideLevelPurchase();
            if (result.ok) {
                SoundManager.playShop();
                this._noticeWindow.showText(`Куплено уровней: ${result.levels}. Списано: ${result.price} Диликов.`, TIER_COLORS[2]);
            }
            else {
                SoundManager.playBuzzer();
                this._noticeWindow.showText(result.reason, ColorManager.crisisColor());
            }
            this.refreshAll();
        }

        claimAll() {
            const count = claimAllSingles();
            if (count > 0) {
                SoundManager.playOk();
                this._noticeWindow.showText(`Получено наград: ${count}. Награды с выбором заберите вручную.`, TIER_COLORS[3]);
            }
            else {
                SoundManager.playBuzzer();
                this._noticeWindow.showText("Доступных одиночных наград нет.", ColorManager.normalColor());
            }
            this.refreshAll();
            this._commandWindow.activate();
        }

        refreshAll() {
            this._headerWindow.refresh();
            this._trackWindow.refresh();
            this._trackWindow.updateDetails();
            this._commandWindow.refresh();
        }
    }

    class Window_DBPChallengesHeader extends Window_Base {
        initialize(rect) {
            super.initialize(rect);
            this.opacity = 0;
            this.refresh();
        }

        refresh() {
            this.contents.clear();
            const active = season();
            const complete = active.challenges.filter(challenge => challenge.complete).length;
            setInterfaceFont(this, 24);
            this.changeTextColor(ColorManager.systemColor());
            this.drawText("НЕДЕЛЬНЫЕ ЗАДАЧИ", 0, 0, 430, "left");
            this.resetTextColor();
            setInterfaceFont(this, 18);
            this.drawText(`Выполнено: ${complete}/${active.challenges.length}`, 450, 2, 250, "center");
            this.changeTextColor(TIER_COLORS[2]);
            this.drawText(`До обновления: ${timeRemainingText()}`, 710, 2, this.innerWidth - 710, "right");
            this.resetTextColor();
        }
    }

    class Scene_DizaliaBattlePassChallenges extends Scene_MenuBase {
        createBackground() {
            this._backgroundSprite = new Sprite(Alloy.Resources.loadImage(MOD_NAME, "DBattlePass_Challenges", { path: ["assets"], prefix: ".png" }));
            this.addChild(this._backgroundSprite);
        }

        create() {
            super.create();
            this._headerWindow = new Window_DBPChallengesHeader(new Rectangle(90, 42, Graphics.boxWidth - 180, 72));
            this.addWindow(this._headerWindow);
            this._challengeWindow = new Window_DBPChallenges(new Rectangle(90, 132, Graphics.boxWidth - 180, 470));
            this.addWindow(this._challengeWindow);
            this._footerWindow = new Window_Base(new Rectangle(90, 625, Graphics.boxWidth - 180, 55));
            this._footerWindow.opacity = 0;
            setInterfaceFont(this._footerWindow, 16);
            this._footerWindow.changeTextColor(TIER_COLORS[2]);
            this._footerWindow.drawText("XP за выполненное задание начисляется автоматически.", 0, 0, this._footerWindow.innerWidth, "center");
            this._footerWindow.resetTextColor();
            this.addWindow(this._footerWindow);
        }

        update() {
            super.update();
            if (Input.isTriggered("cancel")) this.popScene();
        }
    }

    let activePaymentOverlay = null;

    function escapeHtml(value) {
        return String(value)
            .replace(/&/g, "&amp;")
            .replace(/</g, "&lt;")
            .replace(/>/g, "&gt;")
            .replace(/"/g, "&quot;")
            .replace(/'/g, "&#039;");
    }

    function closeHtmlPaymentOverlay() {
        if (!activePaymentOverlay) return;
        if (activePaymentOverlay.timer) clearTimeout(activePaymentOverlay.timer);
        const root = activePaymentOverlay.root;
        activePaymentOverlay = null;
        if (root && root.parentNode) root.parentNode.removeChild(root);
        if (window.Input && Input.clear) Input.clear();
        const canvas = document.querySelector("canvas");
        if (canvas && canvas.focus) canvas.focus();
    }

    function openHtmlPaymentOverlay(options) {
        closeHtmlPaymentOverlay();
        const price = escapeHtml(options.price || "$9.99");
        const root = document.createElement("div");
        root.className = "dbp-checkout";
        root.tabIndex = -1;
        root.innerHTML = `
            <style>
                .dbp-checkout, .dbp-checkout * { box-sizing: border-box; }
                .dbp-checkout { position: fixed; inset: 0; z-index: 100000; display: flex; align-items: center; justify-content: center; padding: 28px; color: #172033; background: rgba(4, 8, 18, .78); font-family: Inter, "Segoe UI", Arial, sans-serif; }
                .dbp-checkout__shell { width: min(1040px, 96vw); min-height: 610px; overflow: hidden; display: grid; grid-template-columns: 42% 58%; border-radius: 18px; background: #fff; box-shadow: 0 28px 90px rgba(0, 0, 0, .52); }
                .dbp-checkout__summary { position: relative; padding: 48px 46px; color: #fff; background: linear-gradient(145deg, #142b5f 0%, #322051 62%, #15172d 100%); }
                .dbp-checkout__summary::after { content: ""; position: absolute; right: -120px; bottom: -150px; width: 340px; height: 340px; border: 1px solid rgba(94, 225, 226, .22); border-radius: 50%; box-shadow: 0 0 70px rgba(65, 214, 218, .12); }
                .dbp-checkout__merchant { display: flex; align-items: center; gap: 13px; margin-bottom: 62px; font-weight: 700; letter-spacing: .04em; }
                .dbp-checkout__mark { display: grid; width: 38px; height: 38px; place-items: center; border-radius: 10px; color: #182143; background: linear-gradient(135deg, #70f0e3, #e4bf67); font-size: 19px; font-weight: 900; }
                .dbp-checkout__eyebrow { margin: 0 0 9px; color: #9bb2d4; font-size: 13px; font-weight: 700; letter-spacing: .12em; text-transform: uppercase; }
                .dbp-checkout__product { margin: 0 0 7px; font-size: 28px; line-height: 1.25; }
                .dbp-checkout__price { margin: 0 0 34px; font-size: 42px; font-weight: 750; letter-spacing: -.03em; }
                .dbp-checkout__benefits { display: grid; gap: 16px; margin: 0; padding: 0; list-style: none; color: #dce8fa; font-size: 14px; line-height: 1.4; }
                .dbp-checkout__benefits li { display: flex; gap: 11px; align-items: flex-start; }
                .dbp-checkout__check { color: #6fe3d5; font-size: 18px; line-height: 1; }
                .dbp-checkout__secure-summary { position: absolute; bottom: 35px; left: 46px; display: flex; gap: 8px; align-items: center; color: #9bb2d4; font-size: 12px; }
                .dbp-checkout__panel { position: relative; padding: 42px 54px 34px; background: #fff; }
                .dbp-checkout__top { display: flex; align-items: center; justify-content: space-between; margin-bottom: 29px; }
                .dbp-checkout__title { margin: 0; color: #172033; font-size: 23px; }
                .dbp-checkout__brands { display: flex; align-items: center; gap: 9px; }
                .dbp-checkout__brand { height: 31px; min-width: 54px; display: grid; place-items: center; padding: 0 9px; border: 1px solid #dce2ec; border-radius: 6px; background: #fff; color: #1d2d73; font-size: 13px; font-style: italic; font-weight: 900; opacity: .55; transition: opacity .15s, border-color .15s, box-shadow .15s; }
                .dbp-checkout__brand.is-active { opacity: 1; border-color: #7b8fd5; box-shadow: 0 0 0 2px rgba(90, 112, 193, .12); }
                .dbp-checkout__mastercard { position: relative; min-width: 57px; }
                .dbp-checkout__mastercard::before, .dbp-checkout__mastercard::after { content: ""; position: absolute; top: 7px; width: 17px; height: 17px; border-radius: 50%; }
                .dbp-checkout__mastercard::before { left: 14px; background: #e74b38; }
                .dbp-checkout__mastercard::after { right: 14px; background: rgba(246, 158, 25, .9); }
                .dbp-checkout__label { display: block; margin: 0 0 8px; color: #344054; font-size: 13px; font-weight: 650; }
                .dbp-checkout__field { margin-bottom: 18px; }
                .dbp-checkout__input-wrap { position: relative; }
                .dbp-checkout__input { width: 100%; height: 48px; padding: 0 14px; border: 1px solid #ccd4e0; border-radius: 8px; outline: none; color: #15213a; background: #fff; font: 15px/1 Inter, "Segoe UI", Arial, sans-serif; transition: border-color .15s, box-shadow .15s; }
                .dbp-checkout__input:focus { border-color: #5368ca; box-shadow: 0 0 0 3px rgba(83, 104, 202, .13); }
                .dbp-checkout__field.is-invalid .dbp-checkout__input { border-color: #d94b5c; box-shadow: 0 0 0 3px rgba(217, 75, 92, .1); }
                .dbp-checkout__error { min-height: 15px; margin-top: 5px; color: #c63247; font-size: 11px; }
                .dbp-checkout__row { display: grid; grid-template-columns: 1fr 1fr; gap: 16px; }
                .dbp-checkout__pay { width: 100%; height: 52px; margin-top: 4px; border: 0; border-radius: 9px; color: #fff; background: linear-gradient(90deg, #5065c9, #6b4ec2); box-shadow: 0 10px 24px rgba(80, 101, 201, .24); cursor: pointer; font-size: 15px; font-weight: 750; letter-spacing: .015em; }
                .dbp-checkout__pay:hover { filter: brightness(1.06); }
                .dbp-checkout__pay:active { transform: translateY(1px); }
                .dbp-checkout__pay:disabled { color: #a7afc0; background: #d9deea; box-shadow: none; cursor: not-allowed; filter: none; transform: none; }
                .dbp-checkout__cancel { display: block; margin: 17px auto 0; border: 0; color: #687287; background: transparent; cursor: pointer; font: 13px Inter, "Segoe UI", Arial, sans-serif; }
                .dbp-checkout__cancel:hover { color: #35415a; text-decoration: underline; }
                .dbp-checkout__footer { margin-top: 18px; color: #9199aa; font-size: 11px; text-align: center; }
                .dbp-checkout__state { height: 490px; display: flex; flex-direction: column; align-items: center; justify-content: center; text-align: center; }
                .dbp-checkout__state-icon { width: 70px; height: 70px; display: grid; place-items: center; margin-bottom: 24px; border-radius: 50%; font-size: 34px; font-weight: 800; }
                .dbp-checkout__state-icon--success { color: #147857; background: #e1f7ef; }
                .dbp-checkout__state-icon--failed { color: #b62f45; background: #ffe8eb; }
                .dbp-checkout__state h2 { margin: 0 0 11px; color: #172033; font-size: 25px; }
                .dbp-checkout__state p { max-width: 390px; margin: 0 0 9px; color: #687287; font-size: 14px; line-height: 1.55; }
                .dbp-checkout__state .dbp-checkout__pay { width: 310px; margin-top: 25px; }
                .dbp-checkout__spinner { width: 54px; height: 54px; margin-bottom: 27px; border: 5px solid #e4e8f2; border-top-color: #5b62ca; border-radius: 50%; animation: dbp-checkout-spin .8s linear infinite; }
                .dbp-checkout__retry-row { width: 310px; display: grid; grid-template-columns: 1fr 1fr; gap: 11px; margin-top: 24px; }
                .dbp-checkout__retry-row .dbp-checkout__pay { width: auto; margin: 0; }
                .dbp-checkout__secondary { height: 52px; border: 1px solid #ccd4e0; border-radius: 9px; color: #4d586e; background: #fff; cursor: pointer; font-size: 14px; font-weight: 650; }
                @keyframes dbp-checkout-spin { to { transform: rotate(360deg); } }
                @media (max-width: 760px) {
                    .dbp-checkout { padding: 12px; overflow: auto; align-items: flex-start; }
                    .dbp-checkout__shell { grid-template-columns: 1fr; }
                    .dbp-checkout__summary { display: none; }
                    .dbp-checkout__panel { padding: 30px 24px; }
                }
            </style>
            <main class="dbp-checkout__shell" role="dialog" aria-modal="true" aria-label="Оплата Premium-пропуска">
                <section class="dbp-checkout__summary">
                    <div class="dbp-checkout__merchant"><span class="dbp-checkout__mark">D</span><span>Dizalia RPG</span></div>
                    <p class="dbp-checkout__eyebrow">Ваш заказ</p>
                    <h1 class="dbp-checkout__product">Battle Pass Premium</h1>
                    <p class="dbp-checkout__price">${price}</p>
                    <ul class="dbp-checkout__benefits">
                        <li><span class="dbp-checkout__check">✓</span><span>Дополнительная линия наград текущего сезона</span></li>
                        <li><span class="dbp-checkout__check">✓</span><span>Мифические и легендарные предметы</span></li>
                        <li><span class="dbp-checkout__check">✓</span><span>Мгновенный доступ к уже открытым уровням</span></li>
                    </ul>
                    <div class="dbp-checkout__secure-summary"><span>▣</span><span>Защищённое соединение</span></div>
                </section>
                <section class="dbp-checkout__panel" data-dbp-panel></section>
            </main>`;
        document.body.appendChild(root);
        activePaymentOverlay = { root, timer: null };

        const panel = root.querySelector("[data-dbp-panel]");
        const stopGameInput = event => event.stopPropagation();
        ["keydown", "keyup", "mousedown", "mouseup", "mousemove", "wheel", "touchstart", "touchmove", "touchend"].forEach(type => {
            root.addEventListener(type, stopGameInput);
        });

        const finishAndClose = callback => {
            closeHtmlPaymentOverlay();
            if (callback) callback();
        };

        const showForm = () => {
            panel.innerHTML = `
                <div class="dbp-checkout__top">
                    <h2 class="dbp-checkout__title">Оплата картой</h2>
                    <div class="dbp-checkout__brands"><span class="dbp-checkout__brand" data-dbp-visa>VISA</span><span class="dbp-checkout__brand dbp-checkout__mastercard" data-dbp-mastercard aria-label="Mastercard"></span></div>
                </div>
                <form data-dbp-form autocomplete="off" novalidate>
                    <div class="dbp-checkout__field" data-field="holder">
                        <label class="dbp-checkout__label" for="dbp-card-holder">Имя владельца карты</label>
                        <input class="dbp-checkout__input" id="dbp-card-holder" name="holder" type="text" autocomplete="cc-name" placeholder="IVAN IVANOV" maxlength="50">
                        <div class="dbp-checkout__error"></div>
                    </div>
                    <div class="dbp-checkout__field" data-field="number">
                        <label class="dbp-checkout__label" for="dbp-card-number">Номер карты</label>
                        <input class="dbp-checkout__input" id="dbp-card-number" name="number" type="text" inputmode="numeric" autocomplete="cc-number" placeholder="0000 0000 0000 0000" maxlength="19">
                        <div class="dbp-checkout__error"></div>
                    </div>
                    <div class="dbp-checkout__row">
                        <div class="dbp-checkout__field" data-field="expiry">
                            <label class="dbp-checkout__label" for="dbp-card-expiry">Срок действия</label>
                            <input class="dbp-checkout__input" id="dbp-card-expiry" name="expiry" type="text" inputmode="numeric" autocomplete="cc-exp" placeholder="ММ/ГГ" maxlength="5">
                            <div class="dbp-checkout__error"></div>
                        </div>
                        <div class="dbp-checkout__field" data-field="cvc">
                            <label class="dbp-checkout__label" for="dbp-card-cvc">Код безопасности</label>
                            <input class="dbp-checkout__input" id="dbp-card-cvc" name="cvc" type="password" inputmode="numeric" autocomplete="cc-csc" placeholder="CVC" maxlength="3">
                            <div class="dbp-checkout__error"></div>
                        </div>
                    </div>
                    <button class="dbp-checkout__pay" type="submit" data-dbp-submit disabled>Оплатить ${price}</button>
                    <button class="dbp-checkout__cancel" type="button" data-dbp-cancel>Вернуться в игру</button>
                    <div class="dbp-checkout__footer">Платёж защищён шифрованием · Dizalia Pay</div>
                </form>`;
            const form = panel.querySelector("[data-dbp-form]");
            const holder = form.elements.holder;
            const number = form.elements.number;
            const expiry = form.elements.expiry;
            const cvc = form.elements.cvc;
            const submit = panel.querySelector("[data-dbp-submit]");
            const visa = panel.querySelector("[data-dbp-visa]");
            const mastercard = panel.querySelector("[data-dbp-mastercard]");
            const setError = (name, message) => {
                const field = form.querySelector(`[data-field="${name}"]`);
                field.classList.toggle("is-invalid", !!message);
                field.querySelector(".dbp-checkout__error").textContent = message || "";
            };
            const cardBrand = digits => {
                if (/^4\d{15}$/.test(digits)) return "visa";
                const prefix2 = Number(digits.slice(0, 2));
                const prefix4 = Number(digits.slice(0, 4));
                if (digits.length === 16 && ((prefix2 >= 51 && prefix2 <= 55) || (prefix4 >= 2221 && prefix4 <= 2720))) return "mastercard";
                return "";
            };
            const expiryIsValid = value => {
                const match = /^(\d{2})\/(\d{2})$/.exec(value);
                if (!match) return false;
                const month = Number(match[1]);
                const year = 2000 + Number(match[2]);
                if (month < 1 || month > 12) return false;
                const today = new Date();
                return year > today.getFullYear() || (year === today.getFullYear() && month >= today.getMonth() + 1);
            };
            const formErrors = () => {
                const digits = number.value.replace(/\D/g, "");
                return {
                    holder: holder.value.trim().length >= 2 ? "" : "Укажите имя владельца карты",
                    number: cardBrand(digits) ? "" : "Карта не найдена",
                    expiry: expiryIsValid(expiry.value) ? "" : "Проверьте срок действия карты",
                    cvc: /^\d{3}$/.test(cvc.value) ? "" : "Введите три цифры"
                };
            };
            const updateSubmitState = () => {
                submit.disabled = Object.values(formErrors()).some(Boolean);
            };
            number.addEventListener("input", () => {
                const digits = number.value.replace(/\D/g, "").slice(0, 16);
                number.value = digits.replace(/(.{4})/g, "$1 ").trim();
                visa.classList.toggle("is-active", digits.startsWith("4"));
                const prefix2 = Number(digits.slice(0, 2));
                const prefix4 = Number(digits.slice(0, 4));
                mastercard.classList.toggle("is-active", (prefix2 >= 51 && prefix2 <= 55) || (prefix4 >= 2221 && prefix4 <= 2720));
                setError("number", "");
                updateSubmitState();
            });
            expiry.addEventListener("input", () => {
                const digits = expiry.value.replace(/\D/g, "").slice(0, 4);
                expiry.value = digits.length > 2 ? `${digits.slice(0, 2)}/${digits.slice(2)}` : digits;
                setError("expiry", "");
                updateSubmitState();
            });
            cvc.addEventListener("input", () => {
                cvc.value = cvc.value.replace(/\D/g, "").slice(0, 3);
                setError("cvc", "");
                updateSubmitState();
            });
            holder.addEventListener("input", () => {
                setError("holder", "");
                updateSubmitState();
            });
            for (const input of [holder, number, expiry, cvc]) {
                input.addEventListener("blur", () => {
                    const errors = formErrors();
                    setError(input.name, errors[input.name]);
                });
            }
            panel.querySelector("[data-dbp-cancel]").addEventListener("click", () => finishAndClose(options.onCancel));
            form.addEventListener("submit", event => {
                event.preventDefault();
                const errors = formErrors();
                Object.keys(errors).forEach(name => setError(name, errors[name]));
                if (Object.values(errors).some(Boolean)) {
                    const invalid = form.querySelector(".is-invalid .dbp-checkout__input");
                    if (invalid) invalid.focus();
                    return;
                }
                showProcessing();
            });
            updateSubmitState();
            holder.focus();
        };

        const showProcessing = () => {
            panel.innerHTML = `
                <div class="dbp-checkout__state">
                    <div class="dbp-checkout__spinner"></div>
                    <h2>Обработка платежа</h2>
                    <p>Подтверждаем операцию в банке-эмитенте.</p>
                    <p>Не закрывайте это окно.</p>
                </div>`;
            activePaymentOverlay.timer = setTimeout(() => {
                if (!activePaymentOverlay || activePaymentOverlay.root !== root) return;
                activePaymentOverlay.timer = null;
                const result = buyPremium();
                if (result.ok) {
                    SoundManager.playShop();
                    showSuccess(result);
                }
                else {
                    SoundManager.playBuzzer();
                    showFailure();
                }
            }, 1200);
        };

        const showSuccess = result => {
            panel.innerHTML = `
                <div class="dbp-checkout__state">
                    <div class="dbp-checkout__state-icon dbp-checkout__state-icon--success">✓</div>
                    <h2>Оплата прошла</h2>
                    <p>Battle Pass Premium активирован для текущего сезона.</p>
                    <p>Номер операции: DZ-${String(now()).slice(-8)}</p>
                    <button class="dbp-checkout__pay" type="button" data-dbp-done>Вернуться в игру</button>
                </div>`;
            panel.querySelector("[data-dbp-done]").addEventListener("click", () => finishAndClose(() => options.onSuccess && options.onSuccess(result)));
        };

        const showFailure = () => {
            panel.innerHTML = `
                <div class="dbp-checkout__state">
                    <div class="dbp-checkout__state-icon dbp-checkout__state-icon--failed">!</div>
                    <h2>Платёж отклонён</h2>
                    <p>Недостаточно средств или банк отклонил операцию.</p>
                    <p>Проверьте доступный баланс и повторите попытку.</p>
                    <div class="dbp-checkout__retry-row">
                        <button class="dbp-checkout__pay" type="button" data-dbp-retry>Попробовать снова</button>
                        <button class="dbp-checkout__secondary" type="button" data-dbp-failed-close>Вернуться</button>
                    </div>
                </div>`;
            panel.querySelector("[data-dbp-retry]").addEventListener("click", showForm);
            panel.querySelector("[data-dbp-failed-close]").addEventListener("click", () => finishAndClose(options.onCancel));
        };

        root.addEventListener("keydown", event => {
            if (event.key === "Escape" && panel.querySelector("[data-dbp-form]")) {
                event.preventDefault();
                finishAndClose(options.onCancel);
            }
        });
        showForm();
    }

    class Window_DBPPaymentTitle extends Window_Base {
        initialize(rect) {
            super.initialize(rect);
            this.opacity = 0;
            this.refresh();
        }
        refresh() {
            this.contents.clear();
            setInterfaceFont(this, 20);
            this.changeTextColor(TIER_COLORS[7]);
            this.drawText("DIZALIA BATTLE PASS · PREMIUM", 0, -4, this.innerWidth, "center");
            this.resetTextColor();
        }
    }

    class Window_DBPPaymentComparison extends Window_Base {
        initialize(rect, premium) {
            this._premium = premium;
            super.initialize(rect);
            this.opacity = 0;
            this.refresh();
        }
        refresh() {
            this.contents.clear();
            setInterfaceFont(this, 18);
            if (this._premium) this.changeTextColor(TIER_COLORS[7]);
            this.drawText(this._premium ? "Premium-пропуск" : "Бесплатный пропуск", 0, -1, this.innerWidth, "center");
            const lines = this._premium
                ? ["Дополнительная линия наград", "Мифические и легендарные предметы", "Все открытые уровни доступны сразу"]
                : ["Награда на каждом уровне", "Максимум: мифическая редкость", "Финальный выбор из 3 предметов"];
            for (let index = 0; index < lines.length; index++) {
                drawFittedText(this, `◆ ${lines[index]}`, 10, 38 + index * 38, this.innerWidth - 20, "left", 16, 12);
            }
            this.resetTextColor();
        }
    }

    class Window_DBPPaymentPrice extends Window_Base {
        initialize(rect) {
            super.initialize(rect);
            this.opacity = 0;
            this.refresh();
        }
        refresh() {
            this.contents.clear();
            setInterfaceFont(this, 15);
            this.changeTextColor(TIER_COLORS[2]);
            this.drawText("PREMIUM НА ТЕКУЩУЮ НЕДЕЛЮ", 0, 10, this.innerWidth, "center");
            setInterfaceFont(this, 34);
            this.changeTextColor(TIER_COLORS[7]);
            this.drawText(setting("premium", "displayPrice", "$9.99"), 0, 42, this.innerWidth, "center");
            this.resetTextColor();
        }
    }

    class Window_DBPPaymentCommands extends Window_HorzCommand {
        initialize(rect) {
            super.initialize(rect);
            this.opacity = 0;
            hideWindowCursor(this);
        }

        maxCols() {
            return 2;
        }

        itemHeight() {
            return 76;
        }

        makeCommandList() {
            this.addCommand(`КУПИТЬ PREMIUM · ${setting("premium", "displayPrice", "$9.99")}`, "buy", true);
            this.addCommand("ОТМЕНА", "cancel", true);
        }

        _refreshCursor() {
            hideWindowCursor(this);
        }

        _updateCursor() {
            hideWindowCursor(this);
        }

        drawItem(index) {
            const rect = this.itemRect(index);
            this.contents.fillRect(rect.x + 3, rect.y + 2, rect.width - 6, rect.height - 4, index === this.index() ? "rgba(213,170,76,0.34)" : "rgba(5,13,24,0.88)");
            this.contents.fillRect(rect.x + 3, rect.y + 2, rect.width - 6, 2, index === this.index() ? TIER_COLORS[7] : "rgba(95,220,222,0.32)");
            setInterfaceFont(this, 19);
            const textY = rect.y + Math.floor((rect.height - this.lineHeight()) / 2);
            drawFittedText(this, this.commandName(index), rect.x + 12, textY, rect.width - 24, "center", 19, 14);
        }
    }

    class Scene_DizaliaBattlePassPayment extends Scene_MenuBase {
        createButtons() {
        }

        createBackground() {
            this._backgroundSprite = new Sprite(Alloy.Resources.loadImage(MOD_NAME, "DBattlePass_Payment", { path: ["assets"], prefix: ".png" }));
            this.addChild(this._backgroundSprite);
        }

        create() {
            super.create();
            this._titleWindow = new Window_DBPPaymentTitle(new Rectangle(400, 96, 480, 52));
            this._freeWindow = new Window_DBPPaymentComparison(new Rectangle(159, 140, 442, 171), false);
            this._premiumWindow = new Window_DBPPaymentComparison(new Rectangle(679, 140, 442, 171), true);
            this._priceWindow = new Window_DBPPaymentPrice(new Rectangle(430, 351, 420, 126));
            this.addWindow(this._titleWindow);
            this.addWindow(this._freeWindow);
            this.addWindow(this._premiumWindow);
            this.addWindow(this._priceWindow);
            this._commandWindow = new Window_DBPPaymentCommands(new Rectangle(280, 580, 720, 106));
            this._commandWindow.setHandler("buy", this.startProcessing.bind(this));
            this._commandWindow.setHandler("cancel", this.popScene.bind(this));
            this.addWindow(this._commandWindow);
        }

        startProcessing() {
            SoundManager.playOk();
            this._commandWindow.deactivate();
            openHtmlPaymentOverlay({
                price: setting("premium", "displayPrice", "$9.99"),
                onSuccess: () => {
                    if (SceneManager._scene === this) this.popScene();
                },
                onCancel: () => {
                    if (SceneManager._scene === this) this._commandWindow.activate();
                }
            });
        }

        terminate() {
            closeHtmlPaymentOverlay();
            super.terminate();
        }
    }

    function installProgressHooks() {
        const _Game_Enemy_performCollapse = Game_Enemy.prototype.performCollapse;
        Game_Enemy.prototype.performCollapse = function() {
            if (!this._dizaliaBattlePassCounted) {
                this._dizaliaBattlePassCounted = true;
                recordProgress("kills", 1);
                if (isBossEnemy(this)) recordProgress("bosses", 1);
            }
            _Game_Enemy_performCollapse.call(this);
        };

        const _Game_Player_increaseSteps = Game_Player.prototype.increaseSteps;
        Game_Player.prototype.increaseSteps = function() {
            _Game_Player_increaseSteps.call(this);
            recordProgress("steps", 1);
        };

        const _Game_Party_gainItem = Game_Party.prototype.gainItem;
        Game_Party.prototype.gainItem = function(item, amount, includeEquip) {
            _Game_Party_gainItem.call(this, item, amount, includeEquip);
            if (amount > 0 && item && !runtime.granting) recordProgress("items", amount);
        };

        const _Game_Party_gainGold = Game_Party.prototype.gainGold;
        Game_Party.prototype.gainGold = function(amount) {
            _Game_Party_gainGold.call(this, amount);
            if (amount > 0 && !runtime.granting) recordProgress("diliks", amount);
        };

        const _DataManager_setupNewGame = DataManager.setupNewGame;
        DataManager.setupNewGame = function() {
            _DataManager_setupNewGame.call(this);
            ensureState(false);
            sanitizeSeasonRewards();
        };

        const _DataManager_extractSaveContents = DataManager.extractSaveContents;
        DataManager.extractSaveContents = function(contents) {
            _DataManager_extractSaveContents.call(this, contents);
            ensureState(false);
            sanitizeSeasonRewards();
        };
    }

    function installMenuCommand() {
        const _Window_MenuCommand_addOriginalCommands = Window_MenuCommand.prototype.addOriginalCommands;
        Window_MenuCommand.prototype.addOriginalCommands = function() {
            _Window_MenuCommand_addOriginalCommands.call(this);
            if (booleanSetting("menu", "showCommand", true)) {
                this.addCommand(setting("menu", "commandName", "Боевой пропуск"), "dizaliaBattlePass", runtime.ready);
            }
        };

        const _Scene_Menu_createCommandWindow = Scene_Menu.prototype.createCommandWindow;
        Scene_Menu.prototype.createCommandWindow = function() {
            _Scene_Menu_createCommandWindow.call(this);
            this._commandWindow.setHandler("dizaliaBattlePass", () => SceneManager.push(Scene_DizaliaBattlePass));
        };
    }

    function installConsoleCommands(api) {
        const commands = Alloy.uniqueCommandFactory(MOD_NAME);
        commands.addCommand("open", () => api.open());
        commands.addCommand("xp", amount => {
            const gained = addXp(amount);
            console.info(`[DizaliaBattlePass] Добавлено XP: ${gained}.`);
        });
        commands.addCommand("premium", () => {
            const active = season();
            active.premium = true;
            console.info("[DizaliaBattlePass] Premium включён для тестирования.");
        });
        commands.addCommand("reset", () => {
            ensureState(true);
            console.info("[DizaliaBattlePass] Создан новый недельный сезон.");
        });
        commands.addInfoCommand("status", log => {
            const active = season();
            log("Dizalia Battle Pass v1.0.0");
            log(`Сезон: ${active.index}`);
            log(`Уровень: ${currentLevel(active)}/${maxLevel()}`);
            log(`XP: ${active.xp}/${requiredXp()}`);
            log(`Premium: ${active.premium ? "да" : "нет"}`);
            log(`До сброса: ${timeRemainingText()}`);
        });
    }

    function bootstrap() {
        rebuildPools();
        runtime.ready = true;
        installProgressHooks();
        installQuestHooks();
        const api = {
            version: "1.0.0",
            open() {
                if (runtime.ready && $gameParty) SceneManager.push(Scene_DizaliaBattlePass);
            },
            state: ensureState,
            season,
            addXp,
            recordProgress,
            claimReward,
            claimAllSingles,
            buyLevels,
            buyPremium,
            forceNewSeason() {
                return ensureState(true).season;
            },
            rewardPools() {
                return runtime.pools;
            },
            claimableRewardCount
        };
        window.DizaliaBattlePass = api;
        if (window.DizaliaGachaHub && typeof window.DizaliaGachaHub.registerPage === "function") {
            window.DizaliaGachaHub.registerPage({
                id: "battlePass",
                title: "БОЕВОЙ ПРОПУСК",
                icon: "DGacha_IconPass",
                order: 50,
                badge: claimableRewardCount,
                open: api.open
            });
        }
        else {
            installMenuCommand();
        }
        installConsoleCommands(api);
    }

    Alloy.afterRegistry(bootstrap);
})();
