<?php
/**
 * The site-event ring buffer: the raw material for culprit attribution.
 *
 * Only a plugin running INSIDE WordPress can see these moments happen — a
 * remote SEO crawler structurally cannot. Everything here is passive
 * observation; nothing is ever blocked or altered.
 *
 * @package SEO_Flight_Recorder
 */

if ( ! defined( 'ABSPATH' ) ) {
	exit;
}

/**
 * Class SFRec_Events
 */
class SFRec_Events {

	const MAX_EVENTS = 60;

	/**
	 * Site options whose change is SEO-relevant enough to record. `blog_public`
	 * is THE catastrophic one (the "Discourage search engines" switch).
	 *
	 * @var string[]
	 */
	private static $watched_options = array(
		'blog_public',
		'permalink_structure',
		'page_on_front',
		'show_on_front',
		'blogname',
		'template',
		'stylesheet',
	);

	/**
	 * Wire the listeners.
	 */
	public static function init() {
		add_action( 'upgrader_process_complete', array( __CLASS__, 'on_upgrade' ), 10, 2 );
		add_action( 'activated_plugin', array( __CLASS__, 'on_plugin_activated' ), 10, 1 );
		add_action( 'deactivated_plugin', array( __CLASS__, 'on_plugin_deactivated' ), 10, 1 );
		add_action( 'switch_theme', array( __CLASS__, 'on_switch_theme' ), 10, 1 );
		add_action( 'post_updated', array( __CLASS__, 'on_post_updated' ), 10, 1 );
		add_action( 'updated_option', array( __CLASS__, 'on_updated_option' ), 10, 1 );
	}

	/**
	 * Record one event.
	 *
	 * @param string $type  Machine type (update_plugin, update_theme, update_core,
	 *                      activate, deactivate, switch_theme, post_edit, option).
	 * @param string $label Human label ("Plugin updated: Elementor 4.1→4.2").
	 * @param int    $post_id Related post ID when the event is a content edit.
	 */
	public static function record( $type, $label, $post_id = 0 ) {
		$events   = get_option( 'sfrec_events', array() );
		$events[] = array(
			't'       => time(),
			'type'    => sanitize_key( $type ),
			'label'   => SFRec_Parser::clean_text( $label ),
			'post_id' => (int) $post_id,
		);
		if ( count( $events ) > self::MAX_EVENTS ) {
			$events = array_slice( $events, -self::MAX_EVENTS );
		}
		update_option( 'sfrec_events', $events, false );
	}

	/**
	 * Events recorded since a timestamp.
	 *
	 * @param int $since Unix timestamp.
	 * @return array[]
	 */
	public static function since( $since ) {
		$events = get_option( 'sfrec_events', array() );
		$since  = (int) $since;
		return array_values(
			array_filter(
				is_array( $events ) ? $events : array(),
				function ( $event ) use ( $since ) {
					return isset( $event['t'] ) && (int) $event['t'] >= $since;
				}
			)
		);
	}

	/**
	 * Upgrader completed: plugins, themes or core.
	 *
	 * @param object $upgrader   WP_Upgrader instance (unused).
	 * @param array  $hook_extra Details of what was updated.
	 */
	public static function on_upgrade( $upgrader, $hook_extra ) {
		unset( $upgrader );
		if ( ! is_array( $hook_extra ) ) {
			return;
		}
		$action = isset( $hook_extra['action'] ) ? $hook_extra['action'] : '';
		$type   = isset( $hook_extra['type'] ) ? $hook_extra['type'] : '';

		if ( 'update' !== $action && 'install' !== $action ) {
			return;
		}

		if ( 'plugin' === $type ) {
			$plugins = array();
			if ( ! empty( $hook_extra['plugins'] ) && is_array( $hook_extra['plugins'] ) ) {
				$plugins = $hook_extra['plugins'];
			} elseif ( ! empty( $hook_extra['plugin'] ) ) {
				$plugins = array( $hook_extra['plugin'] );
			}
			foreach ( $plugins as $plugin_file ) {
				$name = self::plugin_name( $plugin_file );
				/* translators: %s: plugin name. */
				self::record( 'update_plugin', sprintf( __( 'Plugin updated: %s', 'buildsbyluke-seo-flight-recorder' ), $name ) );
			}
			self::schedule_post_update_check();
		} elseif ( 'theme' === $type ) {
			$themes = array();
			if ( ! empty( $hook_extra['themes'] ) && is_array( $hook_extra['themes'] ) ) {
				$themes = $hook_extra['themes'];
			} elseif ( ! empty( $hook_extra['theme'] ) ) {
				$themes = array( $hook_extra['theme'] );
			}
			foreach ( $themes as $theme_slug ) {
				/* translators: %s: theme name. */
				self::record( 'update_theme', sprintf( __( 'Theme updated: %s', 'buildsbyluke-seo-flight-recorder' ), $theme_slug ) );
			}
			self::schedule_post_update_check();
		} elseif ( 'core' === $type ) {
			self::record( 'update_core', __( 'WordPress core updated', 'buildsbyluke-seo-flight-recorder' ) );
			self::schedule_post_update_check();
		}
	}

	/**
	 * Plugin activated.
	 *
	 * @param string $plugin_file Plugin basename.
	 */
	public static function on_plugin_activated( $plugin_file ) {
		if ( SFREC_BASENAME === $plugin_file ) {
			return; // Our own activation is not a suspect.
		}
		/* translators: %s: plugin name. */
		self::record( 'activate', sprintf( __( 'Plugin activated: %s', 'buildsbyluke-seo-flight-recorder' ), self::plugin_name( $plugin_file ) ) );
		self::schedule_post_update_check();
	}

	/**
	 * Plugin deactivated.
	 *
	 * @param string $plugin_file Plugin basename.
	 */
	public static function on_plugin_deactivated( $plugin_file ) {
		if ( SFREC_BASENAME === $plugin_file ) {
			return;
		}
		/* translators: %s: plugin name. */
		self::record( 'deactivate', sprintf( __( 'Plugin deactivated: %s', 'buildsbyluke-seo-flight-recorder' ), self::plugin_name( $plugin_file ) ) );
		self::schedule_post_update_check();
	}

	/**
	 * Theme switched.
	 *
	 * @param string $new_name New theme name.
	 */
	public static function on_switch_theme( $new_name ) {
		/* translators: %s: theme name. */
		self::record( 'switch_theme', sprintf( __( 'Theme switched to: %s', 'buildsbyluke-seo-flight-recorder' ), $new_name ) );
		self::schedule_post_update_check();
	}

	/**
	 * A post was edited. Only recorded for posts behind monitored pages —
	 * everything else is noise for our purposes.
	 *
	 * @param int $post_id Post ID.
	 */
	public static function on_post_updated( $post_id ) {
		global $wpdb;
		$post_id = (int) $post_id;
		if ( $post_id <= 0 ) {
			return;
		}
		$sfrec_is_monitored = (int) $wpdb->get_var( $wpdb->prepare( "SELECT COUNT(*) FROM {$wpdb->prefix}sfrec_pages WHERE post_id = %d AND is_active = 1", $post_id ) ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery -- own table lookup, hot path kept to one COUNT.
		if ( 0 === $sfrec_is_monitored ) {
			return;
		}
		$title = get_the_title( $post_id );
		/* translators: %s: post title. */
		self::record( 'post_edit', sprintf( __( 'Content edited: %s', 'buildsbyluke-seo-flight-recorder' ), $title ? $title : ( '#' . $post_id ) ), $post_id );
	}

	/**
	 * A watched site option changed.
	 *
	 * @param string $option Option name.
	 */
	public static function on_updated_option( $option ) {
		if ( ! in_array( $option, self::$watched_options, true ) ) {
			return;
		}
		if ( 'blog_public' === $option ) {
			$now_public = (string) get_option( 'blog_public' );
			$label      = ( '0' === $now_public )
				? __( 'Site set to DISCOURAGE search engines (blog_public=0)', 'buildsbyluke-seo-flight-recorder' )
				: __( 'Site set to allow search engines (blog_public=1)', 'buildsbyluke-seo-flight-recorder' );
			self::record( 'option', $label );
			self::schedule_post_update_check();
			return;
		}
		/* translators: %s: option name. */
		self::record( 'option', sprintf( __( 'Site setting changed: %s', 'buildsbyluke-seo-flight-recorder' ), $option ) );
		self::schedule_post_update_check();
	}

	/**
	 * Queue the near-immediate re-check that powers "which update did it".
	 * ~90 seconds so page caches and opcode caches settle first; repeated
	 * triggers inside the window collapse into the one scheduled run.
	 */
	public static function schedule_post_update_check() {
		$settings = get_option( 'sfrec_settings', array() );
		if ( empty( $settings['post_update_check'] ) ) {
			return;
		}
		if ( ! wp_next_scheduled( 'sfrec_post_update_check' ) ) {
			wp_schedule_single_event( time() + 90, 'sfrec_post_update_check' );
		}
	}

	/**
	 * Resolve a plugin basename to its display name (falls back to the file).
	 *
	 * @param string $plugin_file Plugin basename.
	 * @return string
	 */
	private static function plugin_name( $plugin_file ) {
		if ( ! function_exists( 'get_plugin_data' ) ) {
			require_once ABSPATH . 'wp-admin/includes/plugin.php';
		}
		$path = WP_PLUGIN_DIR . '/' . $plugin_file;
		if ( file_exists( $path ) ) {
			$data = get_plugin_data( $path, false, false );
			if ( ! empty( $data['Name'] ) ) {
				$version = ! empty( $data['Version'] ) ? ' ' . $data['Version'] : '';
				return $data['Name'] . $version;
			}
		}
		return (string) $plugin_file;
	}
}
