<?php
if (class_exists('PeepSoMaintenanceFactory')) {

	/**
	 * PeepSo3_Maintenance_Activity_Ranking
	 *
	 * Thin cron driver for the content ranking engine. Every public static method
	 * of a PeepSoMaintenanceFactory subclass is auto-run on the 5-minute
	 * CRON_MAINTENANCE_EVENT tick, so this class deliberately exposes ONE public
	 * method (ranking()) that orchestrates the passes in the required order; all
	 * heavy lifting lives in PeepSoActivityRankingEngine.
	 */
	class PeepSo3_Maintenance_Activity_Ranking extends PeepSoMaintenanceFactory {

		/**
		 * Run ranking on its OWN dedicated 1-minute cron (peepso_ranking_event), NOT the
		 * shared 5-minute maintenance tick — so fresh engagement is reflected within ~1 min
		 * and the reconcile/backfill passes get more throughput, without dragging the other
		 * ~15 maintenance tasks down to a 1-minute cadence. See PeepSo::CRON_RANKING_EVENT
		 * scheduling in install/activate.php + the self-healing check in peepso.php.
		 */
		public function __construct() {
			$this->event = PeepSo::CRON_RANKING_EVENT;
			parent::__construct();
		}

		/**
		 * Run the ranking maintenance passes, in order:
		 *   1. Event tail      — flag roots for engagement written straight to the
		 *                        source tables (seeder / import), bypassing mark_dirty.
		 *   2. Dirty recompute — reflect events flagged since the last tick.
		 *   3. Aging reconcile — age the 30-day windows out + self-heal drift (fast wrap).
		 *   4. Long reconcile  — slow, rate-limited wrap over the 1-YEAR set to keep the
		 *                        365-day + long-half-life Hot columns fresh, without
		 *                        slowing the fast 30-day set.
		 *   5. Backfill        — one-time population of legacy content after upgrade.
		 *
		 * Hot scores are NOT decayed here: they are stored with a per-row anchor
		 * (rank_hot_ts) and decayed lazily at read time via
		 * PeepSoActivityRankingEngine::hot_order_sql(), so there is no periodic
		 * full-table rewrite.
		 *
		 * @return int Total rows touched (for the maintenance debug timer).
		 */
		public static function ranking() {
			// Only run once the DB migration that adds/renames ALL score columns has landed
			// (recompute() writes rank_score_365d, rank_score_hot_1d/7d/30d/365d, rank_rev — the
			// #8037 weights-generation stamp added at 318 — and rank_score_all, the all-time Top
			// column added at 319).
			if (intval(get_option('peepso_database_version', 0)) < 319) {
				return 0;
			}

			if (!class_exists('PeepSoActivityRankingEngine')) {
				return 0;
			}

			// Cross-request concurrency lock. A MySQL named lock is atomic (no get-then-set race a
			// transient has) and auto-releases when the DB connection closes — so a mid-run fatal or
			// PHP timeout never strands ranking: the next 1-minute tick just re-acquires and picks up
			// where the cursors left off. Non-blocking (timeout 0): if another tick — the cron, the
			// gauge poll, or ?peepso_process_ranking — already holds it, skip this one.
			global $wpdb;
			if (1 !== intval($wpdb->get_var($wpdb->prepare("SELECT GET_LOCK(%s, 0)", 'peepso_ranking_run')))) {
				return 0;
			}

			$count = 0;

			$count += PeepSoActivityRankingEngine::process_event_tail();
			$count += PeepSoActivityRankingEngine::process_dirty();
			$count += PeepSoActivityRankingEngine::process_reconcile();
			$count += PeepSoActivityRankingEngine::process_reconcile_long();
			$count += PeepSoActivityRankingEngine::backfill_step();

			// Hidden rollout: flip the sticky reveal flag once the initial index hits the threshold
			// (one-time; a no-op once revealed). Keeps the frontend Top/Hot picker hidden until then.
			PeepSoActivityRankingEngine::reveal_rollout_if_ready();

			$wpdb->query($wpdb->prepare("SELECT RELEASE_LOCK(%s)", 'peepso_ranking_run'));

			return intval($count);
		}

	}
}

// EOF
