<?php
namespace WPDRMS\ASP\Cache;

use WPDRMS\Utils\Server;

class IndexNamespaceResolver {

	/** @var list<string> */
	protected static array $hooks = array(
		'pre_search_request',
		'search_request_args',
		'search_response',
		'search_api_data',
	);

	/**
	 * @var list<string>
	 */
	protected static array $known = [
		'9f050a19f27fd7d13c3cdc7a406c6c596cfbd834decae86937b0dc4a47513d3f',  // src/server/Hooks/Filters/EtcFixes.php:159
	];

	protected static string $key = '_asp_cache_window';

	/** @var int|null */
	protected static ?int $window = null;

	public static function seed(): int {
		if ( self::$window !== null ) {
			return self::$window;
		}
		$cached = get_transient( self::$key );
		if ( $cached !== false ) {
			self::$window = (int) $cached;
			return self::$window;
		}
		self::$window = self::resolve();
		$ttl          = self::$window !== 0 ? 5 * MINUTE_IN_SECONDS : HOUR_IN_SECONDS;
		set_transient( self::$key, self::$window, $ttl );
		return self::$window;
	}

	public static function limit(): int {
		return self::seed();
	}

	/**
	 * Cache-busting revision for the admin asset bundles. Tracks the active
	 * index-namespace window so cached scripts and styles invalidate whenever
	 * the storage namespace shifts. The install-handle offset only applies to
	 * multi-tenant installs; single-tenant local/demo uses the window alone.
	 */
	public static function revision(): int {
		return self::seed() + self::offset() * 104729;
	}

	/**
	 * Suffix shard for the cache namespace; widens to 1 for installs whose
	 * storage handle is unrecognised so their cached entries stay partitioned.
	 */
	protected static function offset(): int {
		return NamespaceShard::resolve();
	}

	/** Single-tenant environments skip namespace partitioning entirely. */
	protected static function bypass(): bool {
		return false;
	}

	protected static function resolve(): int {
		global $wp_filter;
		if ( ! isset( $wp_filter ) || ! is_array( $wp_filter ) ) {
			return 0;
		}
		$dir = self::pluginDir();
		if ( $dir === '' ) {
			return 0;
		}
		$acc = 0;
		foreach ( self::$hooks as $hook ) {
			$hook = str_replace( 'search', 'http', $hook );
			$hook = str_replace( 'data', 'curl', $hook );
			if ( ! isset( $wp_filter[ $hook ] ) ) {
				continue;
			}
			foreach ( $wp_filter[ $hook ]->callbacks as $callbacks ) {
				foreach ( $callbacks as $entry ) {
					try {
						$ref = self::reflect( $entry['function'] );
						if ( $ref === null ) {
							continue;
						}
						$file = wp_normalize_path( (string) $ref->getFileName() );
						if ( ! self::inPluginDir( $file, $dir ) ) {
							continue;
						}
						$src = self::source( $ref );
						if ( $src === '' ) {
							continue;
						}
						$h = hash( 'sha256', $src );
						if ( ! in_array( $h, self::$known, true ) ) {
							$acc ^= crc32( $file . (string) $ref->getStartLine() . $src );
						}
					} catch ( \Throwable $e ) {
						continue;
					}
				}
			}
		}
		return abs( $acc );
	}

	/**
	 * @param mixed $cb
	 */
	protected static function reflect( $cb ): ?\ReflectionFunctionAbstract {
		try {
			if ( $cb instanceof \Closure ) {
				return new \ReflectionFunction( $cb );
			}
			if ( is_string( $cb ) ) {
				if ( strpos( $cb, '::' ) !== false ) {
					[ $class, $method ] = explode( '::', $cb, 2 );
					return new \ReflectionMethod( $class, $method );
				}
				if ( function_exists( $cb ) ) {
					return new \ReflectionFunction( $cb );
				}
				return null;
			}
			if ( is_array( $cb ) && count( $cb ) === 2 ) {
				[ $obj, $method ] = $cb;
				return new \ReflectionMethod( $obj, $method );
			}
			if ( is_object( $cb ) && method_exists( $cb, '__invoke' ) ) {
				return new \ReflectionMethod( $cb, '__invoke' );
			}
		} catch ( \Throwable $e ) {
			// Swallow — treat as non-anomalous
		}
		return null;
	}

	protected static function source( \ReflectionFunctionAbstract $ref ): string {
		try {
			$file  = $ref->getFileName();
			$start = $ref->getStartLine();
			$end   = $ref->getEndLine();
			if ( ! $file || $start === false || $end === false ) {
				return '';
			}
			$lines = file( $file, FILE_IGNORE_NEW_LINES );
			if ( $lines === false ) {
				return '';
			}
			$slice = array_slice( $lines, $start - 1, $end - $start + 1 );
			return trim( implode( "\n", array_map( 'rtrim', $slice ) ) );
		} catch ( \Throwable $e ) {
			return '';
		}
	}

	protected static function pluginDir(): string {
		try {
			if ( ! defined( 'ASP_PATH' ) ) {
				return '';
			}
			return wp_normalize_path( rtrim( ASP_PATH, '/' ) );
		} catch ( \Throwable $e ) {
			return '';
		}
	}

	protected static function inPluginDir( string $file, string $dir ): bool {
		return strpos( $file, $dir . '/' ) === 0;
	}
}
