/** * @fileoverview Content Rotation State, Queue Selection Algorithms & Scoring Matrix * @module state/rotation-state * @description إدارة حالة مكتبة التدوير، خوارزميات اختيار المنشور التالي المتوازن، وحساب نقاط كفاءة المنشورات. */ (global => { 'use strict'; const STATE_INTEGRITY_SALT = global.FritreeAppConstants?.SECURITY_SALTS?.ROTATION_MATRIX_SALT || "FritreeRotationMatrixDynamicIntegrityVerificationSalt_2026"; /** * الحالة المركزية لمكتبة تدوير المنشورات */ const RotationState = { previousPostsData: [], postGroupsData: [], selectedPrevPostIds: new Set(), selectedFollowupIds: new Set(), selectedGroupIds: new Set(), isRotationActive: false, addPrevPostMedia: [], editPrevPostMedia: [], activeObjectUrls: new Set(), currentPage: 1, itemsPerPage: 9, viewMode: 'cards', groupViewMode: 'cards', activeGroupFilter: 'all', isLoaded: false }; /** * حساب ختم الأمان الرقمي لمصفوفة المنشورات ومجموعات التدوير * @returns {Promise} */ async function calculateStateChecksum() { const postsConcat = (RotationState.previousPostsData || []).map(p => `${p.id}:${p.status}`).sort().join('||'); const groupsConcat = (RotationState.postGroupsData || []).map(g => `${g.id}:${g.name}:${(g.postIds || []).length}`).sort().join('||'); const structuralConcat = `${postsConcat}###${groupsConcat}`; if (global.FritreeCrypto && typeof global.FritreeCrypto.signReceipt === 'function') { return await global.FritreeCrypto.signReceipt( "ROT_MATRIX", structuralConcat.length, "verify", structuralConcat, STATE_INTEGRITY_SALT ); } return "lite_hash_" + structuralConcat.length; } /** * حفظ الختم الرقمي لتأمين مكتبة التدوير */ async function saveDynamicStateChecksum() { const computedSignature = await calculateStateChecksum(); if (global.FritreeStorage) { await global.FritreeStorage.set('local_rotation_matrix_integrity_sig', computedSignature); } } /** * حفظ وتخزين المنشورات والمجموعات بالتخزين المشفر */ async function savePrevPostsToLocal() { if (!global.FritreeStorage || !Array.isArray(RotationState.previousPostsData)) return; await global.FritreeStorage.set('local_previous_posts', RotationState.previousPostsData); await global.FritreeStorage.set('local_post_groups', RotationState.postGroupsData || []); await saveDynamicStateChecksum(); } /** * تحميل حالة مكتبة التدوير من التخزين المشفر */ async function loadRotationState() { if (!global.FritreeStorage) return; let rawPrevPosts = await global.FritreeStorage.get('local_previous_posts', null); if (Array.isArray(rawPrevPosts) && rawPrevPosts.length > 0) { RotationState.previousPostsData = rawPrevPosts.filter(p => p && typeof p.id === 'string'); } else { RotationState.previousPostsData = []; } let rawPostGroups = await global.FritreeStorage.get('local_post_groups', null); if (Array.isArray(rawPostGroups)) { RotationState.postGroupsData = rawPostGroups; } else { RotationState.postGroupsData = []; } const cachedSelected = await global.FritreeStorage.get('local_selected_prev_posts', []); RotationState.selectedPrevPostIds = new Set(Array.isArray(cachedSelected) ? cachedSelected.filter(id => typeof id === 'string') : []); const activeState = await global.FritreeStorage.get('local_is_rotation_active', 'false'); RotationState.isRotationActive = (activeState === 'true'); recalculateAllPostScores(); RotationState.isLoaded = true; } /** * إعادة حساب كفاءة ودرجة نجاح المنشورات (Success Scoring Engine) */ function recalculateAllPostScores() { if (!RotationState.previousPostsData || RotationState.previousPostsData.length === 0) return; const metrics = RotationState.previousPostsData.map(p => { const usage = p.stats ? (p.stats.usage || 0) : (p.usageCount || 0); const successCount = p.stats ? ((p.stats.success || 0) + (p.stats.pending || 0)) : (p.usageCount || 0); const ratio = usage > 0 ? (successCount / usage) : 0; return { id: p.id, ratio, usage }; }); const maxUsage = Math.max(...metrics.map(m => m.usage), 1); RotationState.previousPostsData.forEach(p => { const metric = metrics.find(m => m.id === p.id); if (!p.stats) p.stats = { usage: p.usageCount || 0, success: p.usageCount || 0, pending: 0, failed: 0 }; const successWeightedPoints = (metric ? metric.ratio : 0) * 70; const consistencyWeightedPoints = (metric ? (metric.usage / maxUsage) : 0) * 30; p.score = Math.min(100, Math.max(0, Math.round(successWeightedPoints + consistencyWeightedPoints))); }); } /** * تسجيل وتحديث إحصائيات استخدام المنشور بعد كل عملية نشر * @param {string} id - معرّف المنشور * @param {string} status - حالة النشر ('success' | 'pending' | 'failed') */ async function incrementPostUsage(id, status = 'success') { const post = RotationState.previousPostsData.find(x => x.id === id); if (post) { if (!post.stats) post.stats = { usage: 0, success: 0, failed: 0, pending: 0 }; post.stats.usage = (post.stats.usage || 0) + 1; post.usageCount = (post.usageCount || 0) + 1; if (status === 'success') post.stats.success++; else if (status === 'pending') post.stats.pending++; else if (status === 'failed') post.stats.failed++; recalculateAllPostScores(); await savePrevPostsToLocal(); if (global.FritreeRotation && typeof global.FritreeRotation.renderPreviousPostsGrid === 'function') { global.FritreeRotation.renderPreviousPostsGrid(); } } } /** * خوارزمية اختيار المنشور التالي في التدوير بناءً على النمط المحدد * @param {string} mode - النمط ('balanced' | 'weighted' | 'high_perf' | 'newest' | 'oldest' | 'random') * @param {Array} availableIds - قائمة المنشورات المتاحة * @param {string|null} targetGroupId - معرّف الحزمة المخصصة إن وُجدت * @returns {Object|null} كائن المنشور المختار */ function selectNextRotationPost(mode, availableIds, targetGroupId = null) { let pool = RotationState.previousPostsData.filter(p => p.status === 'active'); if (targetGroupId) { const groupObj = (RotationState.postGroupsData || []).find(g => g.id === targetGroupId); if (groupObj && Array.isArray(groupObj.postIds)) { pool = pool.filter(p => groupObj.postIds.includes(p.id)); } } else if (availableIds && availableIds.length > 0) { pool = pool.filter(p => availableIds.includes(p.id)); } if (pool.length === 0) return null; switch (mode) { case 'newest': return [...pool].sort((a, b) => new Date(b.entryDate || b.date) - new Date(a.entryDate || a.date))[0]; case 'oldest': return [...pool].sort((a, b) => new Date(a.entryDate || a.date) - new Date(b.entryDate || b.date))[0]; case 'high_perf': return [...pool].sort((a, b) => (b.score || 0) - (a.score || 0))[0]; case 'weighted': let totalPriority = pool.reduce((acc, curr) => acc + (parseInt(curr.priority, 10) || 50), 0); let thresh = Math.random() * totalPriority; let cumulative = 0; for (let c of pool) { cumulative += (parseInt(c.priority, 10) || 50); if (thresh <= cumulative) return c; } return pool[0]; case 'balanced': return [...pool].sort((a, b) => { const usageA = a.stats ? (a.stats.usage || 0) : (a.usageCount || 0); const usageB = b.stats ? (b.stats.usage || 0) : (b.usageCount || 0); return usageA - usageB; })[0]; case 'random': default: return pool[Math.floor(Math.random() * pool.length)]; } } /** * تعطيل وضع التدوير الشامل للتطبيق */ async function disableRotationGlobally() { RotationState.isRotationActive = false; if (global.FritreeStorage) { await global.FritreeStorage.set('local_is_rotation_active', 'false'); } if (global.FritreeRotation) { if (typeof global.FritreeRotation.updateRotationIndicatorUI === 'function') global.FritreeRotation.updateRotationIndicatorUI(); if (typeof global.FritreeRotation.updateRotationBadgeInModal === 'function') global.FritreeRotation.updateRotationBadgeInModal(); } const chk = document.getElementById('iukycc01s4'); if (chk) chk.checked = false; } /** * تصدير وحدة حالة التدوير المركزية */ global.FritreeRotationState = RotationState; global.FritreeRotationStore = { state: RotationState, load: loadRotationState, save: savePrevPostsToLocal, recalculateScores: recalculateAllPostScores, incrementUsage: incrementPostUsage, selectNextPost: selectNextRotationPost, disableRotation: disableRotationGlobally }; })(typeof globalThis !== 'undefined' ? globalThis : typeof window !== 'undefined' ? window : this);