<?php
/**
 * Precompiled Resources Cache for Easy Social Share Buttons
 *
 * Manages pre-compiled CSS/JS resources with flexible storage options.
 * Supports multiple storage locations and automatic folder management.
 *
 * @package EasySocialShareButtons
 * @author  appscreo
 * @since   1.0.0
 * @version 2.0.0
 */

// Exit if accessed directly.
if ( ! defined( 'ABSPATH' ) ) {
	exit;
}

/**
 * Class ESSB_Precompiled_Cache
 *
 * Pre-compiled resource caching system with flexible storage.
 *
 * Features:
 * - Multiple storage location support (cache, uploads, plugin, custom)
 * - Organized folder structure
 * - Automatic expiration handling
 * - Resource URL generation
 * - Unique identifier management
 * - Shared cache directory with other components
 *
 * @since 1.0.0
 */
class ESSB_Precompiled_Cache {

	/**
	 * Cache version for invalidation.
	 *
	 * @since 2.0.0
	 * @var string
	 */
	const CACHE_VERSION = '2.0.0';

	/**
	 * Shared cache directory name (used by multiple components).
	 *
	 * @since 2.0.0
	 * @var string
	 */
	const SHARED_CACHE_DIR = 'essb-cache';

	/**
	 * Precompiled resources subdirectory.
	 *
	 * @since 2.0.0
	 * @var string
	 */
	const COMPILED_SUBDIR = 'compiled';

	/**
	 * Cache folder path.
	 *
	 * @since 1.0.0
	 * @var string
	 */
	public static $cacheFolder = '';

	/**
	 * Cache URL.
	 *
	 * @since 1.0.0
	 * @var string
	 */
	public static $cacheURL = '';

	/**
	 * Cache expiration time in seconds (1 year).
	 *
	 * @since 1.0.0
	 * @var int
	 */
	public static $cacheTime = YEAR_IN_SECONDS;

	/**
	 * Whether cache is active.
	 *
	 * @since 1.0.0
	 * @var bool
	 */
	public static $isActive = false;

	/**
	 * Shared cache base directory.
	 *
	 * @since 2.0.0
	 * @var string
	 */
	private static $shared_cache_base = '';

	/**
	 * Activate precompiled resources cache.
	 *
	 * Sets up cache directory in wp-content/cache with shared directory structure.
	 * Creates necessary directories and defines constants.
	 *
	 * @since 1.0.0
	 * @since 2.0.0 Simplified to use only wp-content/cache location.
	 *
	 * @return bool True on success, false on failure.
	 */
	public static function activate() {
		// Always use wp-content/cache (WordPress standard).
		$base_path = WP_CONTENT_DIR . '/cache/' . self::SHARED_CACHE_DIR . '/' . self::COMPILED_SUBDIR . '/';
		$base_url  = content_url( 'cache/' . self::SHARED_CACHE_DIR . '/' . self::COMPILED_SUBDIR . '/' );

		// Allow custom folder via filter (for advanced users).
		if ( has_filter( 'essb_precompiled_folder' ) ) {
			$base_path = apply_filters( 'essb_precompiled_folder', $base_path );
		}

		// Allow custom URL via filter (for advanced users).
		if ( has_filter( 'essb_precompiled_url' ) ) {
			$base_url = apply_filters( 'essb_precompiled_url', $base_url );
		}

		// SSL support.
		if ( is_ssl() ) {
			$base_url = str_replace( 'http://', 'https://', $base_url );
		}

		// Store shared cache base (parent of compiled folder).
		$shared_base = dirname( untrailingslashit( $base_path ) );
		if ( ! is_dir( $shared_base ) ) {
			if ( ! wp_mkdir_p( $shared_base ) ) {
				return false;
			}
		}
		self::$shared_cache_base = trailingslashit( $shared_base );

		// Create compiled directory.
		if ( ! is_dir( $base_path ) ) {
			if ( ! wp_mkdir_p( $base_path ) ) {
				return false;
			}

			// Add .htaccess for caching.
			self::create_htaccess( $base_path );
		}

		self::$cacheFolder = $base_path;
		self::$cacheURL    = $base_url;

		// Define constant.
		if ( ! defined( 'ESSB3_PRECOMPILED_RESOURCE' ) ) {
			define( 'ESSB3_PRECOMPILED_RESOURCE', true );
		}

		self::$isActive = true;

		return true;
	}

	/**
	 * Create .htaccess file in cache directory.
	 *
	 * @since 2.0.0
	 *
	 * @param string $cache_dir Cache directory path.
	 * @return void
	 */
	private static function create_htaccess( $cache_dir ) {
		$htaccess_content = "# ESSB Precompiled Cache
<IfModule mod_expires.c>
ExpiresActive On
ExpiresByType text/css \"access plus 1 year\"
ExpiresByType application/javascript \"access plus 1 year\"
</IfModule>

<IfModule mod_headers.c>
Header set Cache-Control \"public, max-age=31536000, immutable\"
</IfModule>
";

		$htaccess_file = $cache_dir . '.htaccess';
		// phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_file_put_contents
		@file_put_contents( $htaccess_file, $htaccess_content );
	}

	/**
	 * Store data in cache.
	 *
	 * @since 1.0.0
	 *
	 * @param string $id   Cache key identifier.
	 * @param string $data Data to cache.
	 * @return bool True on success, false on failure.
	 */
	public static function put( $id = '', $data = '' ) {
		if ( ! self::$isActive || empty( $id ) ) {
			return false;
		}

		$id       = self::key_parser( $id );
		$filename = self::$cacheFolder . $id . '_cache.txt';

		// phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_file_put_contents
		if ( ! @file_put_contents( $filename, $data ) ) {
			return false;
		}

		return true;
	}

	/**
	 * Store resource file in cache (CSS/JS).
	 *
	 * @since 1.0.0
	 * @since 2.0.0 Removed transient usage to prevent database flooding.
	 *
	 * @param string $id   Cache key identifier.
	 * @param string $data Data to cache.
	 * @param string $type File type ('css' or 'js').
	 * @return bool True on success, false on failure.
	 */
	public static function put_resource( $id = '', $data = '', $type = 'css' ) {
		if ( ! self::$isActive || empty( $id ) ) {
			return false;
		}

		$id       = self::key_parser( $id );
		$filename = self::$cacheFolder . $id . '.' . $type;

		// phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_file_put_contents
		if ( ! @file_put_contents( $filename, $data ) ) {
			return false;
		}

		return true;
	}

	/**
	 * Parse cache key to MD5 hash.
	 *
	 * @since 1.0.0
	 *
	 * @param string $id Cache key identifier.
	 * @return string MD5 hash of the key.
	 */
	public static function key_parser( $id ) {
		$id = strtolower( $id );
		$id = str_replace( ' ', '_', $id );
		$id = md5( $id );

		return $id;
	}

	/**
	 * Retrieve data from cache.
	 *
	 * Validates file existence before returning data.
	 * Deletes expired files automatically.
	 *
	 * @since 1.0.0
	 * @since 2.0.0 Added automatic expired file deletion.
	 *
	 * @param string $id Cache key identifier.
	 * @return string Cached data or empty string if not found/expired.
	 */
	public static function get( $id = '' ) {
		if ( ! self::$isActive || empty( $id ) ) {
			return '';
		}

		$id       = self::key_parser( $id );
		$filename = self::$cacheFolder . $id . '_cache.txt';

		// Check if file actually exists.
		if ( ! file_exists( $filename ) ) {
			return '';
		}

		// Check expiration.
		$age = ( time() - filemtime( $filename ) );
		if ( $age >= self::$cacheTime ) {
			// File expired, delete it.
			// phpcs:ignore WordPress.WP.AlternativeFunctions.unlink_unlink
			@unlink( $filename );
			return '';
		}

		// phpcs:ignore WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents
		$data = @file_get_contents( $filename );
		return $data ? $data : '';
	}

	/**
	 * Retrieve resource URL from cache (CSS/JS).
	 *
	 * Validates file existence before returning URL.
	 * Deletes expired files automatically.
	 *
	 * @since 1.0.0
	 * @since 2.0.0 Added automatic expired file deletion.
	 *
	 * @param string $id   Cache key identifier.
	 * @param string $type File type ('css' or 'js').
	 * @return string Cache URL or empty string if not found/expired.
	 */
	public static function get_resource( $id = '', $type = 'css' ) {
		if ( ! self::$isActive || empty( $id ) ) {
			return '';
		}

		$id       = self::key_parser( $id );
		$filename = self::$cacheFolder . $id . '.' . $type;

		// Check if file actually exists.
		if ( ! file_exists( $filename ) ) {
			return '';
		}

		// Check expiration.
		$age = ( time() - filemtime( $filename ) );
		if ( $age >= self::$cacheTime ) {
			// File expired, delete it.
			// phpcs:ignore WordPress.WP.AlternativeFunctions.unlink_unlink
			@unlink( $filename );
			return '';
		}

		return self::$cacheURL . $id . '.' . $type;
	}

	/**
	 * Flush entire precompiled cache.
	 *
	 * Removes all files from the compiled directory only.
	 * Does not affect other components using the shared essb-cache directory.
	 *
	 * @since 1.0.0
	 * @since 2.0.0 Only clears compiled subfolder, preserves shared cache structure.
	 *
	 * @return bool Always returns false for compatibility.
	 */
	public static function flush() {
		if ( ! self::$isActive || empty( self::$cacheFolder ) ) {
			return false;
		}

		// Only remove files from compiled folder, not the folder itself.
		if ( is_dir( self::$cacheFolder ) ) {
			$files = glob( self::$cacheFolder . '*' );
			if ( is_array( $files ) ) {
				foreach ( $files as $file ) {
					if ( is_file( $file ) && '.htaccess' !== basename( $file ) ) {
						// phpcs:ignore WordPress.WP.AlternativeFunctions.unlink_unlink
						@unlink( $file );
					}
				}
			}
		}

		return false;
	}

	/**
	 * Flush single cache entry.
	 *
	 * @since 1.0.0
	 *
	 * @param string $id Cache key identifier.
	 * @return bool True on success, false if not found.
	 */
	public static function flush_single( $id ) {
		if ( ! self::$isActive || empty( $id ) ) {
			return false;
		}

		$id       = self::key_parser( $id );
		$filename = self::$cacheFolder . $id . '_cache.txt';

		if ( file_exists( $filename ) ) {
			// phpcs:ignore WordPress.WP.AlternativeFunctions.unlink_unlink
			return @unlink( $filename );
		}

		return false;
	}

	/**
	 * Recursively remove directory and contents.
	 *
	 * @since 1.0.0
	 * @deprecated 2.0.0 No longer used, kept for backward compatibility.
	 *
	 * @param string $directory Directory path to remove.
	 * @return void
	 */
	public static function recursiveRemoveDirectory( $directory ) {
		if ( ! is_dir( $directory ) ) {
			return;
		}

		$items = glob( $directory . '/*' );

		if ( false === $items ) {
			$items = array();
		}

		foreach ( $items as $item ) {
			if ( is_dir( $item ) ) {
				self::recursiveRemoveDirectory( $item );
			} else {
				// phpcs:ignore WordPress.WP.AlternativeFunctions.unlink_unlink
				@unlink( $item );
			}
		}

		// phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_rmdir
		@rmdir( $directory );
	}

	/**
	 * Get relative path of an asset.
	 *
	 * @since 1.0.0
	 *
	 * @param string $base_url Base URL.
	 * @param string $item_url Item URL.
	 * @return string|false Relative path or false if not local.
	 */
	public static function get_asset_relative_path( $base_url, $item_url ) {
		// Remove protocol reference from base URL.
		$base_url = preg_replace( '/^(https?:\/\/|\/\/)/i', '', $base_url );

		// Check if this is a local asset.
		$src_parts = explode( $base_url, $item_url );

		// Get the trailing part of the URL.
		$maybe_relative = end( $src_parts );

		// Verify file exists.
		if ( ! file_exists( ABSPATH . $maybe_relative ) ) {
			return false;
		}

		return $maybe_relative;
	}

	/**
	 * Purge all precompiled cache transients.
	 *
	 * Note: Transients no longer created in v2.0.0+, but this cleans up legacy ones.
	 *
	 * @since 1.0.0
	 * @since 2.0.0 Improved with prepared statements.
	 * @deprecated 2.0.0 Transients no longer used to prevent database flooding.
	 *
	 * @return bool True if transients were deleted, false otherwise.
	 */
	public static function purge_essb_cache_static_transients() {
		global $wpdb;

		// Clean up precompiled cache transients.
		// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
		$transients = $wpdb->get_col(
			$wpdb->prepare(
				"SELECT option_name FROM {$wpdb->options} 
				WHERE option_name LIKE %s OR option_name LIKE %s",
				$wpdb->esc_like( '_transient_timeout_essb_precache_static-' ) . '%',
				$wpdb->esc_like( '_transient_essb_precache_static-' ) . '%'
			)
		);

		if ( ! empty( $transients ) ) {
			foreach ( $transients as $transient ) {
				$name = str_replace( array( '_transient_timeout_', '_transient_' ), '', $transient );
				delete_transient( $name );
			}
		}

		// Clean up unique key transients.
		// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
		$key_transients = $wpdb->get_col(
			$wpdb->prepare(
				"SELECT option_name FROM {$wpdb->options} 
				WHERE option_name LIKE %s OR option_name LIKE %s",
				$wpdb->esc_like( '_transient_timeout_essb_precache_unique_key' ) . '%',
				$wpdb->esc_like( '_transient_essb_precache_unique_key' ) . '%'
			)
		);

		if ( ! empty( $key_transients ) ) {
			foreach ( $key_transients as $transient ) {
				$name = str_replace( array( '_transient_timeout_', '_transient_' ), '', $transient );
				delete_transient( $name );
			}
		}

		return true;
	}

	/**
	 * Generate or retrieve unique identifier.
	 *
	 * Generates a random 20-character string and stores it in options.
	 * Validates that cache folder exists, regenerates if folder was deleted.
	 *
	 * @since 1.0.0
	 * @since 2.0.0 Changed from transient to option for reliability, added validation.
	 *
	 * @return string Unique identifier.
	 */
	public static function get_unique_identifier() {
		$stored_key = get_option( 'essb_precache_unique_key' );

		// If key exists but cache folder doesn't exist, regenerate the key.
		if ( ! empty( $stored_key ) && ! is_dir( self::$cacheFolder ) ) {
			delete_option( 'essb_precache_unique_key' );
			$stored_key = '';
		}

		if ( empty( $stored_key ) ) {
			$stored_key = self::generate_string( 20 );
			update_option( 'essb_precache_unique_key', $stored_key, false );
		}

		return $stored_key;
	}

	/**
	 * Generate random string.
	 *
	 * @since 1.0.0
	 *
	 * @param int $strength Length of string to generate.
	 * @return string Random string.
	 */
	public static function generate_string( $strength = 16 ) {
		$input        = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
		$input_length = strlen( $input );
		$random_string = '';

		for ( $i = 0; $i < $strength; $i++ ) {
			$random_character = $input[ mt_rand( 0, $input_length - 1 ) ];
			$random_string   .= $random_character;
		}

		return $random_string;
	}

	/**
	 * Get shared cache base directory.
	 *
	 * Returns the essb-cache directory shared by multiple components.
	 *
	 * @since 2.0.0
	 *
	 * @return string Shared cache base directory path.
	 */
	public static function get_shared_cache_base() {
		return self::$shared_cache_base;
	}

	/**
	 * Get cache statistics.
	 *
	 * @since 2.0.0
	 *
	 * @return array Cache statistics.
	 */
	public static function get_cache_stats() {
		if ( ! self::$isActive ) {
			return array(
				'active'      => false,
				'folder'      => '',
				'url'         => '',
				'total_files' => 0,
				'total_size'  => '0 B',
			);
		}

		$files = glob( self::$cacheFolder . '*' );
		$count = is_array( $files ) ? count( $files ) : 0;
		$size  = 0;

		if ( is_array( $files ) ) {
			foreach ( $files as $file ) {
				if ( is_file( $file ) ) {
					$size += filesize( $file );
				}
			}
		}

		return array(
			'active'      => true,
			'folder'      => self::$cacheFolder,
			'url'         => self::$cacheURL,
			'total_files' => $count,
			'total_size'  => size_format( $size, 2 ),
		);
	}
}

/**
 * Backward compatibility class alias.
 *
 * Allows old code using ESSBPrecompiledResources to continue working.
 *
 * @since 2.0.0
 */
class_alias( 'ESSB_Precompiled_Cache', 'ESSBPrecompiledResources' );

// ============================================================================
// Modern Helper Functions
// ============================================================================

/**
 * Activate precompiled cache.
 *
 * @since 2.0.0
 *
 * @return bool True on success, false on failure.
 */
function essb_activate_precompiled_cache() {
	return ESSB_Precompiled_Cache::activate();
}

/**
 * Store data in precompiled cache.
 *
 * @since 2.0.0
 *
 * @param string $key  Cache key.
 * @param string $data Data to cache.
 * @return bool True on success, false on failure.
 */
function essb_precompiled_cache_put( $key, $data ) {
	return ESSB_Precompiled_Cache::put( $key, $data );
}

/**
 * Retrieve data from precompiled cache.
 *
 * @since 2.0.0
 *
 * @param string $key Cache key.
 * @return string Cached data or empty string.
 */
function essb_precompiled_cache_get( $key ) {
	return ESSB_Precompiled_Cache::get( $key );
}

/**
 * Store resource in precompiled cache.
 *
 * @since 2.0.0
 *
 * @param string $key  Cache key.
 * @param string $data File content.
 * @param string $type File type ('css' or 'js').
 * @return bool True on success, false on failure.
 */
function essb_precompiled_cache_put_resource( $key, $data, $type = 'css' ) {
	return ESSB_Precompiled_Cache::put_resource( $key, $data, $type );
}

/**
 * Get resource URL from precompiled cache.
 *
 * @since 2.0.0
 *
 * @param string $key  Cache key.
 * @param string $type File type ('css' or 'js').
 * @return string Resource URL or empty string.
 */
function essb_precompiled_cache_get_resource( $key, $type = 'css' ) {
	return ESSB_Precompiled_Cache::get_resource( $key, $type );
}

/**
 * Flush precompiled cache.
 *
 * @since 2.0.0
 *
 * @return bool Always returns false for compatibility.
 */
function essb_flush_precompiled_cache() {
	return ESSB_Precompiled_Cache::flush();
}

/**
 * Flush single precompiled cache entry.
 *
 * @since 2.0.0
 *
 * @param string $key Cache key.
 * @return bool True on success, false on failure.
 */
function essb_flush_precompiled_cache_single( $key ) {
	return ESSB_Precompiled_Cache::flush_single( $key );
}

/**
 * Get precompiled cache statistics.
 *
 * @since 2.0.0
 *
 * @return array Cache statistics.
 */
function essb_get_precompiled_cache_stats() {
	return ESSB_Precompiled_Cache::get_cache_stats();
}

/**
 * Check if precompiled cache is active.
 *
 * @since 2.0.0
 *
 * @return bool True if cache is active, false otherwise.
 */
function essb_is_precompiled_cache_active() {
	return ESSB_Precompiled_Cache::$isActive;
}

/**
 * Get precompiled cache folder path.
 *
 * @since 2.0.0
 *
 * @return string Cache folder path.
 */
function essb_get_precompiled_cache_folder() {
	return ESSB_Precompiled_Cache::$cacheFolder;
}

/**
 * Get precompiled cache URL.
 *
 * @since 2.0.0
 *
 * @return string Cache URL.
 */
function essb_get_precompiled_cache_url() {
	return ESSB_Precompiled_Cache::$cacheURL;
}

/**
 * Get shared cache base directory.
 *
 * Returns the essb-cache directory used by multiple components.
 *
 * @since 2.0.0
 *
 * @return string Shared cache base directory.
 */
function essb_get_shared_cache_base() {
	return ESSB_Precompiled_Cache::get_shared_cache_base();
}

/**
 * Get or generate unique identifier for cache.
 *
 * @since 2.0.0
 *
 * @return string Unique identifier.
 */
function essb_get_cache_unique_identifier() {
	return ESSB_Precompiled_Cache::get_unique_identifier();
}