<?php
/**
 * Sitemap Manager
 *
 * Entry point for the custom sitemap module. Disables core sitemaps when
 * enabled and registers custom routing.
 *
 * @package SEOvaultSEO
 */

namespace SEOvaultSEO\Sitemap;

use SEOvaultSEO\Core\Options;

/**
 * Manages the custom sitemap module
 */
class Manager {

	/**
	 * Option set after one runtime rewrite heal attempt.
	 * Prevents flush_rewrite_rules() on every request when rules stay missing.
	 */
	const REWRITE_HEAL_OPTION = 'seovaultseo_sitemap_rewrite_heal_attempted';

	/**
	 * Initialize sitemap manager.
	 */
	public static function init() {
		if ( ! \SEOvaultSEO\Compatibility\Detector::should_output_seo() ) {
			return;
		}

		// Custom sitemap off: keep core-WP helpers only (noindex exclusion + leftover cron cleanup).
		if ( ! self::is_enabled() ) {
			add_filter( 'wp_sitemaps_posts_query', array( __CLASS__, 'exclude_noindexed_posts' ), 10, 2 );
			if ( wp_next_scheduled( Scheduler::EVENT_NAME ) ) {
				Scheduler::unschedule();
			}
			return;
		}

		add_action( 'init', array( __CLASS__, 'setup' ), 9999 );
		add_filter( 'wp_sitemaps_enabled', array( __CLASS__, 'filter_core_sitemap_enabled' ) );

		self::register_invalidation_hooks();
		Cache::init();
		Scheduler::init();
		Rest::init();
	}

	/**
	 * Register cache invalidation hooks.
	 */
	private static function register_invalidation_hooks() {
		add_action( 'save_post', array( __CLASS__, 'invalidate_post' ), 20, 1 );
		add_action( 'delete_post', array( __CLASS__, 'invalidate_post' ), 20, 1 );
		add_action( 'transition_post_status', array( __CLASS__, 'invalidate_post_status_transition' ), 20, 3 );

		add_action( 'created_term', array( __CLASS__, 'invalidate_term' ), 20, 3 );
		add_action( 'edited_term', array( __CLASS__, 'invalidate_term' ), 20, 3 );
		add_action( 'delete_term', array( __CLASS__, 'invalidate_term' ), 20, 3 );

		add_action( 'user_register', array( __CLASS__, 'invalidate_user' ), 20, 1 );
		add_action( 'delete_user', array( __CLASS__, 'invalidate_user' ), 20, 1 );
		add_action( 'profile_update', array( __CLASS__, 'invalidate_user' ), 20, 1 );

		add_action( 'seovaultseo_save_settings', array( __CLASS__, 'invalidate_all' ), 20, 0 );
	}

	/**
	 * Invalidate cache for a post.
	 *
	 * @param int $post_id Post ID.
	 */
	public static function invalidate_post( $post_id ) {
		if ( ! self::is_enabled() ) {
			return;
		}
		$post = get_post( $post_id );
		if ( ! $post ) {
			return;
		}
		Builder::rebuild_for_post( $post_id );
		self::schedule_background_rebuild();
	}

	/**
	 * Invalidate cache on post status transition.
	 *
	 * @param string $new_status New status.
	 * @param string $old_status Old status.
	 * @param object $post Post object.
	 */
	public static function invalidate_post_status_transition( $new_status, $old_status, $post ) {
		if ( ! self::is_enabled() ) {
			return;
		}
		if ( 'publish' === $new_status || 'publish' === $old_status ) {
			self::invalidate_post( $post->ID );
		}
	}

	/**
	 * Invalidate cache for a term.
	 *
	 * @param int    $term_id Term ID.
	 * @param int    $tt_id Term taxonomy ID.
	 * @param string $taxonomy Taxonomy.
	 */
	public static function invalidate_term( $term_id, $tt_id, $taxonomy ) {
		if ( ! self::is_enabled() ) {
			return;
		}
		Builder::rebuild_for_term( $taxonomy, $term_id );
		self::schedule_background_rebuild();
	}

	/**
	 * Invalidate cache for a user.
	 *
	 * @param int $user_id User ID.
	 */
	public static function invalidate_user( $user_id ) {
		if ( ! self::is_enabled() ) {
			return;
		}
		Builder::rebuild_for_users();
		self::schedule_background_rebuild();
	}

	/**
	 * Invalidate all cache.
	 */
	public static function invalidate_all() {
		if ( ! self::is_enabled() ) {
			return;
		}
		Cache::clear_all();
		self::schedule_background_rebuild();
	}

	/**
	 * Schedule a background rebuild.
	 */
	private static function schedule_background_rebuild() {
		if ( ! wp_next_scheduled( 'seovaultseo_sitemap_rebuild' ) ) {
			wp_schedule_single_event( time() + 5, 'seovaultseo_sitemap_rebuild' );
		}
	}

	/**
	 * Check if the custom sitemap is enabled.
	 *
	 * @return bool
	 */
	public static function is_enabled() {
		return Options::is_sitemap_enabled();
	}

	/**
	 * Setup the custom sitemap if enabled.
	 */
	public static function setup() {
		if ( ! self::is_enabled() ) {
			return;
		}

		Router::register();
		self::ensure_persisted_rewrite_rules();
	}

	/**
	 * Ensure the sitemap rewrite rules are persisted in the rewrite_rules option.
	 *
	 * Self-heals at most once when rules are missing. Further repairs happen on
	 * plugin activation or sitemap settings save (which clear the heal flag).
	 */
	private static function ensure_persisted_rewrite_rules() {
		if ( wp_installing() ) {
			return;
		}

		if ( self::has_persisted_rewrite_rules() ) {
			return;
		}

		// Already attempted a runtime heal — do not flush on every request.
		if ( get_option( self::REWRITE_HEAL_OPTION ) ) {
			return;
		}

		if ( did_action( 'wp_loaded' ) ) {
			self::flush_persisted_rewrite_rules();
			return;
		}

		if ( ! has_action( 'wp_loaded', array( __CLASS__, 'flush_persisted_rewrite_rules' ) ) ) {
			add_action( 'wp_loaded', array( __CLASS__, 'flush_persisted_rewrite_rules' ), 1 );
		}
	}

	/**
	 * Check whether the key sitemap pretty-permalink rules are already stored.
	 *
	 * @return bool
	 */
	private static function has_persisted_rewrite_rules() {
		$rules = get_option( 'rewrite_rules' );

		return is_array( $rules )
			&& isset( $rules['seovault-sitemap\.xml$'] )
			&& isset( $rules['seovault-sitemap\.xsl$'] );
	}

	/**
	 * Flush rewrite rules once the full rewrite stack is available.
	 *
	 * The heal option gates further runtime attempts; do not use a process-static
	 * flag (PHPUnit and long-lived workers would permanently skip the option write).
	 */
	public static function flush_persisted_rewrite_rules() {
		flush_rewrite_rules( false );
		update_option( self::REWRITE_HEAL_OPTION, '1', false );
	}

	/**
	 * Allow a future one-time runtime heal (call from activation / settings save).
	 *
	 * @return void
	 */
	public static function clear_rewrite_heal_flag() {
		delete_option( self::REWRITE_HEAL_OPTION );
	}

	/**
	 * Filter WordPress core sitemap enabled state.
	 *
	 * @param bool $enabled Current state.
	 * @return bool
	 */
	public static function filter_core_sitemap_enabled( $enabled ) {
		if ( self::is_enabled() ) {
			return false;
		}
		return $enabled;
	}

	/**
	 * Get the custom sitemap index URL.
	 *
	 * @return string
	 */
	public static function get_index_url() {
		return home_url( '/seovault-sitemap.xml' );
	}

	/**
	 * Get sitemap URL (backward-compatible alias).
	 *
	 * @return string
	 */
	public static function get_sitemap_url() {
		return self::get_index_url();
	}

	/**
	 * Get sitemap status.
	 *
	 * @return array
	 */
	public static function get_status() {
		$enabled  = self::is_enabled();
		$last     = Options::get_sitemap_last_generated();
		$has_data = ! empty( Cache::read_index() );

		return array(
			'enabled'        => $enabled,
			'url'            => $enabled ? self::get_index_url() : '',
			'type'           => 'custom',
			'cached'         => $has_data,
			'last_generated' => $last ? gmdate( 'Y-m-d H:i:s', $last ) : '',
		);
	}

	/**
	 * Exclude noindexed posts from the WordPress core sitemap.
	 *
	 * @param array  $args Query args.
	 * @param string $post_type Post type.
	 * @return array
	 */
	public static function exclude_noindexed_posts( $args, $post_type ) {
		$noindexed_posts = Query::get_noindexed_post_ids( $post_type );

		if ( ! empty( $noindexed_posts ) ) {
			// phpcs:ignore WordPressVIPMinimum.Performance.WPQueryParams.PostNotIn_post__not_in
			$args['post__not_in'] = array_merge( $args['post__not_in'] ?? array(), $noindexed_posts );
		}

		return $args;
	}
}
