<?php

namespace Waypoint\Agent\Admin;

use Waypoint\Agent\Base\Thing;

class Flush_Everything extends Thing {

	const AJAX_ACTION  = 'waypoint_agent_flush';
	const NONCE_ACTION = 'waypoint_agent_flush';

	public function attach_hooks(): void {
		add_action( 'admin_bar_menu', [ $this, 'add_node' ], 999 );
		add_action( 'wp_ajax_' . self::AJAX_ACTION, [ $this, 'handle_ajax' ] );
		add_action( 'wp_footer', [ $this, 'output_script' ] );
		add_action( 'admin_footer', [ $this, 'output_script' ] );
	}

	public function add_node( \WP_Admin_Bar $bar ): void {
		if ( ! current_user_can( 'manage_options' ) ) {
			return;
		}
		$bar->add_node( [
			'id'    => 'waypoint-flush',
			'title' => '&#9889; Flush Everything',
			'href'  => '#',
			'meta'  => [ 'title' => 'Clear all caches and regenerate assets' ],
		] );
	}

	public function handle_ajax(): void {
		check_ajax_referer( self::NONCE_ACTION );
		if ( ! current_user_can( 'manage_options' ) ) {
			wp_send_json_error( 'Unauthorized', 403 );
		}
		wp_send_json_success( [ 'log' => $this->run() ] );
	}

	public function run(): array {
		$log = [];

		// 1. WordPress object cache (Redis / Memcached / file)
		wp_cache_flush();
		$log[] = 'Object cache: flushed';

		// 2. WP Rocket
		$log = array_merge( $log, $this->flush_wp_rocket() );

		// 3. EWWW Image Optimizer / Easy IO
		$log = array_merge( $log, $this->flush_ewww() );

		// 4. SpinupWP server-level cache (always last -- outermost layer)
		$log = array_merge( $log, $this->flush_spinupwp() );

		return $log;
	}

	// -------------------------------------------------------------------------
	// WP Rocket
	// -------------------------------------------------------------------------

	private function flush_wp_rocket(): array {
		if ( ! defined( 'WP_ROCKET_VERSION' ) ) {
			return [];
		}

		$log = [];

		// Page cache. In WP Rocket 3.11+ this also clears per-URL RUCSS entries.
		if ( function_exists( 'rocket_clean_domain' ) ) {
			rocket_clean_domain();
			$log[] = 'WP Rocket: page cache cleared';
		}

		// Minified CSS/JS -- public function varies by WP Rocket version.
		$log[] = $this->clear_rocket_minify();

		// Critical CSS (Async CSS feature). Deleting the files causes WP Rocket
		// to regenerate them on the next real page request.
		if ( defined( 'WP_ROCKET_CRITICAL_CSS_PATH' ) && is_dir( WP_ROCKET_CRITICAL_CSS_PATH ) ) {
			$files = glob( WP_ROCKET_CRITICAL_CSS_PATH . '*.css' ) ?: [];
			array_map( 'unlink', $files );
			$log[] = sprintf(
				'WP Rocket: critical CSS cleared (%d file(s), regenerates on next visit)',
				count( $files )
			);
		}

		// Config file (htaccess rules, combined options snapshot)
		if ( function_exists( 'rocket_regenerate_config_file' ) ) {
			rocket_regenerate_config_file();
			$log[] = 'WP Rocket: config regenerated';
		}

		// Above-the-fold / LCP detection data. WP Rocket's JavaScript beacon detects
		// the LCP element on each page visit and stores the result in wpr_above_the_fold
		// so it can output <link rel="preload"> on future requests. This data is NOT
		// cleared by rocket_clean_domain() -- it persists across page cache flushes.
		// Truncating the table forces the beacon to re-detect on the next real visit.
		$log[] = $this->clear_rocket_atf_data();

		return $log;
	}

	private function clear_rocket_atf_data(): string {
		global $wpdb;

		$table = $wpdb->prefix . 'wpr_above_the_fold';

		// Verify the table exists before touching it -- absent on sites without OCI feature.
		$exists = $wpdb->get_var( $wpdb->prepare( 'SHOW TABLES LIKE %s', $table ) );
		if ( $exists !== $table ) {
			return 'WP Rocket: ATF/LCP table not present (OCI feature inactive)';
		}

		$count  = (int) $wpdb->get_var( "SELECT COUNT(*) FROM `{$table}`" ); // phpcs:ignore WordPress.DB
		$wpdb->query( "TRUNCATE TABLE `{$table}`" ); // phpcs:ignore WordPress.DB

		return sprintf(
			'WP Rocket: LCP/ATF detection data cleared (%d row(s) -- beacon re-detects on next desktop visit)',
			$count
		);
	}

	private function clear_rocket_minify(): string {
		// Try documented public function names across WP Rocket versions.
		foreach ( [ 'rocket_clean_minify', 'rocket_clean_minified_domain' ] as $fn ) {
			if ( function_exists( $fn ) ) {
				$fn();
				return 'WP Rocket: minified CSS/JS cleared';
			}
		}

		// Fallback: determine the minify cache directory from known constants.
		$min_path = null;
		if ( defined( 'WP_ROCKET_MINIFY_CACHE_PATH' ) ) {
			$min_path = WP_ROCKET_MINIFY_CACHE_PATH;
		} elseif ( defined( 'WP_ROCKET_CACHE_ROOT_PATH' ) ) {
			$min_path = WP_ROCKET_CACHE_ROOT_PATH . 'min/';
		}

		if ( $min_path ) {
			$n = $this->clear_directory( $min_path );
			return $n >= 0
				? "WP Rocket: minified assets cleared ({$n} files)"
				: 'WP Rocket: minify cache dir not found (skipped)';
		}

		return 'WP Rocket: minify cache path unknown (skipped)';
	}

	// -------------------------------------------------------------------------
	// EWWW Image Optimizer / Easy IO / ExactDN
	// -------------------------------------------------------------------------

	private function flush_ewww(): array {
		if ( ! defined( 'EWWW_IMAGE_OPTIMIZER_PLUGIN_FILE' ) ) {
			return [];
		}

		global $exactdn;

		if ( ! $exactdn ) {
			return [ 'EWWW: active (Easy IO / ExactDN not running)' ];
		}

		if ( ! method_exists( $exactdn, 'flush_cache' ) ) {
			return [ 'EWWW ExactDN: active but flush_cache() not available in this version' ];
		}

		try {
			$exactdn->flush_cache();
			return [ 'EWWW ExactDN: cache flushed' ];
		} catch ( \Throwable $e ) {
			return [ 'EWWW ExactDN: flush failed -- ' . $e->getMessage() ];
		}
	}

	// -------------------------------------------------------------------------
	// SpinupWP
	// -------------------------------------------------------------------------

	private function flush_spinupwp(): array {
		if ( ! function_exists( 'spinupwp' ) ) {
			return [];
		}

		try {
			$instance = spinupwp();
			// Try every known public method name across SpinupWP plugin versions.
			foreach ( [ 'purge_page_cache', 'purge_cache', 'purge_entire_cache' ] as $method ) {
				if ( method_exists( $instance, $method ) ) {
					$instance->{$method}();
					return [ 'SpinupWP: server cache purged' ];
				}
			}
			return [ 'SpinupWP: active but no purgeable method found' ];
		} catch ( \Throwable $e ) {
			return [ 'SpinupWP: purge failed -- ' . $e->getMessage() ];
		}
	}

	// -------------------------------------------------------------------------
	// Helpers
	// -------------------------------------------------------------------------

	private function clear_directory( string $dir ): int {
		if ( ! is_dir( $dir ) ) {
			return -1;
		}
		$count = 0;
		$items = new \RecursiveIteratorIterator(
			new \RecursiveDirectoryIterator( $dir, \FilesystemIterator::SKIP_DOTS ),
			\RecursiveIteratorIterator::CHILD_FIRST
		);
		foreach ( $items as $item ) {
			if ( $item->isDir() ) {
				@rmdir( $item->getPathname() ); // phpcs:ignore WordPress.PHP.NoSilencedErrors
			} else {
				@unlink( $item->getPathname() ); // phpcs:ignore WordPress.PHP.NoSilencedErrors
				$count++;
			}
		}
		return $count;
	}

	// -------------------------------------------------------------------------
	// Admin bar inline script
	// -------------------------------------------------------------------------

	public function output_script(): void {
		if ( ! is_admin_bar_showing() || ! current_user_can( 'manage_options' ) ) {
			return;
		}
		?>
		<script>
		(function () {
			var node = document.getElementById('wp-admin-bar-waypoint-flush');
			if (!node) return;
			node.querySelector('a').addEventListener('click', function (e) {
				e.preventDefault();
				var link = e.currentTarget;
				var orig  = link.innerHTML;
				link.innerHTML           = '&#9203; Flushing&hellip;';
				link.style.opacity       = '.6';
				link.style.pointerEvents = 'none';

				fetch(<?php echo wp_json_encode( admin_url( 'admin-ajax.php' ) ); ?>, {
					method: 'POST',
					credentials: 'same-origin',
					headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
					body: new URLSearchParams({
						action: <?php echo wp_json_encode( self::AJAX_ACTION ); ?>,
						_ajax_nonce: <?php echo wp_json_encode( wp_create_nonce( self::NONCE_ACTION ) ); ?>
					})
				})
				.then(function (r) { return r.json(); })
				.then(function (data) {
					link.innerHTML = data.success ? '&#10003; Done' : '&#10007; Error';
					if (data.success && data.data && data.data.log) {
						console.group('%cFlush Everything', 'font-weight:bold');
						data.data.log.forEach(function (m) { console.log(m); });
						console.groupEnd();
					}
					setTimeout(function () {
						link.innerHTML           = orig;
						link.style.opacity       = '';
						link.style.pointerEvents = '';
					}, 3000);
				})
				.catch(function () {
					link.innerHTML = '&#10007; Failed';
					setTimeout(function () {
						link.innerHTML           = orig;
						link.style.opacity       = '';
						link.style.pointerEvents = '';
					}, 3000);
				});
			});
		})();
		</script>
		<?php
	}
}
