*/ protected array $debug_decision_log = []; /** * Logs a decision for a specific item. * Merges reasons if an entry already exists to provide a full history. * * @param int|string $item_id The ID of the item being processed. * @param string $status The machine-readable status. * @param string|null $reason (Optional) Human-readable explanation. */ public function log_decision( int|string $item_id, string $status, ?string $reason = null ): void { // Check if we already have a log for this item if ( isset( $this->debug_decision_log[ $item_id ] ) ) { $previous_entry = $this->debug_decision_log[ $item_id ]; // Merge Reason: Keep history if ( $reason ) { $existing_reason = $previous_entry['reason'] ?? ''; // Only append if it's not exactly the same text. if ( $existing_reason && $existing_reason !== $reason ) { if ( ! str_contains( $existing_reason, $reason ) ) { $reason = $existing_reason . ' ➝ ' . $reason; } else { $reason = $existing_reason; } } } else { $reason = $previous_entry['reason']; } } $this->debug_decision_log[ $item_id ] = [ 'status' => $status, // The new status wins. 'reason' => $reason, 'time' => microtime( true ), ]; } /** * Resets the decision log. */ public function reset_debug_log(): void { $this->debug_decision_log = []; } /** * Retrieves the decision log for a specific item. * * @param int|string $item_id * @return array|null Returns null if the item was not part of the process. */ public function get_decision_for( int|string $item_id ): ?array { return $this->debug_decision_log[ $item_id ] ?? null; } /** * Retrieves the entire decision log. * Used by the RuntimeDebugger/SnapshotBuilder to generate the frontend JSON. * * @return array */ public function get_all_decisions(): array { return $this->debug_decision_log; } /** * Helper: Checks if a specific item was explicitly rejected. * Useful for internal logic checks. * * @param int|string $item_id * @return bool */ public function is_rejected( int|string $item_id ): bool { $decision = $this->get_decision_for( $item_id ); if ( ! $decision ) { return false; // Not processed means not strictly "rejected" by logic } // Checks for statuses like 'rejected_condition', 'rejected_limit', 'rejected_invalid' return str_starts_with( $decision['status'], 'rejected_' ); } /** * Helper: Checks if a specific item was selected. * * @param int|string $item_id * @return bool */ public function is_selected( int|string $item_id ): bool { $decision = $this->get_decision_for( $item_id ); return $decision && $decision['status'] === 'selected'; } }