export const tutorialProgressKey = campaignId => `hex-trails-tutorial-v1-${campaignId}`; function tutorialCount(count) { return Number.isInteger(count) && count > 0 ? count : 0; } function normalizeProgress(value, count) { const valid = index => Number.isInteger(index) && index >= 0 && index < count; return { current: valid(value?.current) ? value.current : 0, completed: [...new Set(Array.isArray(value?.completed) ? value.completed.filter(valid) : [])], }; } export function readTutorialProgress(storage, campaignId, count) { count = tutorialCount(count); let value; if (count) { try { value = JSON.parse(storage?.getItem(tutorialProgressKey(campaignId)) ?? '{}'); } catch {} } return normalizeProgress(value, count); } export function writeTutorialProgress(storage, campaignId, count, progress) { count = tutorialCount(count); if (!count || !storage) return false; try { storage.setItem(tutorialProgressKey(campaignId), JSON.stringify(normalizeProgress(progress, count))); return true; } catch { return false; } } export function tutorialLaunch(search, count, progress, campaignProgress) { count = tutorialCount(count); if (!count) return null; const params = new URLSearchParams(search); const requested = params.get('tutorial'); if (/^\d+$/.test(requested ?? '')) { const index = Number(requested) - 1; if (Number.isInteger(index) && index >= 0 && index < count) return index; } // Existing level links keep their destination, including links to level 1. if (params.has('level')) return null; if ((Number.isInteger(campaignProgress?.current) && campaignProgress.current > 0) || (Array.isArray(campaignProgress?.completed) && campaignProgress.completed.length > 0)) return null; const normalized = normalizeProgress(progress, count); const completed = new Set(normalized.completed); for (let offset = 0; offset < count; offset += 1) { const index = (normalized.current + offset) % count; if (!completed.has(index)) return index; } return null; }