<?php
/**
 * Dynamic Cache Manager for Easy Social Share Buttons
 *
 * Provides file-based caching for button HTML and resource files (CSS/JS).
 * Supports automatic expiration, cache flushing, and organized storage.
 *
 * @package EasySocialShareButtons
 * @author  appscreo
 * @since   10.8.2
 * @version 2.0.0
 */

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

/**
 * Class ESSB_Dynamic_Cache
 *
 * File-based caching system for dynamic content and resources.
 *
 * Features:
 * - Organized folder structure (buttons, css, js, data)
 * - Automatic cache expiration
 * - MD5-based cache keys
 * - Legacy cache migration
 * - WordPress standard cache location
 * - Individual and bulk cache clearing
 *
 * @since 1.0.0
 */
class ESSB_Dynamic_Cache {

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

	/**
	 * Cache directory name.
	 *
	 * @since 2.0.0
	 * @var string
	 */
	const CACHE_DIR_NAME = 'essb-cache';

	/**
	 * Legacy cache directory name.
	 *
	 * @since 2.0.0
	 * @var string
	 */
	const LEGACY_CACHE_DIR_NAME = 'essb_cache';

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

	/**
	 * Cache base 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;

	/**
	 * Cache mode (buttons, resource, full).
	 *
	 * @since 2.0.0
	 * @var string
	 */
	private static $cache_mode = '';

	/**
	 * Cache subdirectories.
	 *
	 * @since 2.0.0
	 * @var array
	 */
	private static $subdirs = array(
		'buttons' => 'buttons',
		'css'     => 'css',
		'js'      => 'js',
		'data'    => 'data',
	);

	/**
	 * Activate dynamic cache.
	 *
	 * Initializes cache system with specified mode and creates necessary directories.
	 *
	 * @since 1.0.0
	 * @since 2.0.0 Updated to use wp-content/cache, organized subfolders.
	 *
	 * @param string $cache_mode Cache mode: 'buttons', 'resource', or 'full'.
	 * @return bool True on success, false on failure.
	 */
	public static function activate( $cache_mode = '' ) {
		if ( empty( $cache_mode ) ) {
			$cache_mode = 'full';
		}

		self::$cache_mode = $cache_mode;

		// Setup cache paths.
		$cache_paths = self::setup_cache_paths();

		if ( false === $cache_paths ) {
			return false;
		}

		self::$cacheFolder = $cache_paths['dir'];
		self::$cacheURL    = $cache_paths['url'];

		// Create subdirectories.
		self::create_cache_subdirectories();

		// Migrate legacy cache if exists.
		self::migrate_legacy_cache();

		// Set cache mode constants.
		if ( 'buttons' === $cache_mode ) {
			if ( ! defined( 'ESSB3_CACHE_ACTIVE' ) ) {
				define( 'ESSB3_CACHE_ACTIVE', true );
			}
		} elseif ( 'resource' === $cache_mode ) {
			if ( ! defined( 'ESSB3_CACHE_ACTIVE_RESOURCE' ) ) {
				define( 'ESSB3_CACHE_ACTIVE_RESOURCE', true );
			}
		} else {
			if ( ! defined( 'ESSB3_CACHE_ACTIVE' ) ) {
				define( 'ESSB3_CACHE_ACTIVE', true );
			}
			if ( ! defined( 'ESSB3_CACHE_ACTIVE_RESOURCE' ) ) {
				define( 'ESSB3_CACHE_ACTIVE_RESOURCE', true );
			}
		}

		self::$isActive = true;

		return true;
	}

	/**
	 * Setup cache directory paths.
	 *
	 * Priority: wp-content/cache > wp-content/uploads
	 *
	 * @since 2.0.0
	 *
	 * @return array|false Array with 'dir' and 'url' keys, or false on failure.
	 */
	private static function setup_cache_paths() {
		// Try wp-content/cache first (WordPress standard).
		$cache_dir = WP_CONTENT_DIR . '/cache/' . self::CACHE_DIR_NAME . '/dynamic';
		$cache_url = content_url( 'cache/' . self::CACHE_DIR_NAME . '/dynamic' );

		// Check if wp-content/cache is writable.
		$wp_content_cache = WP_CONTENT_DIR . '/cache';
		if ( ! is_dir( $wp_content_cache ) ) {
			wp_mkdir_p( $wp_content_cache );
		}

		// Test write permissions.
		if ( ! is_writable( $wp_content_cache ) ) {
			// Fallback to uploads directory.
			$upload_dir = wp_upload_dir();
			$cache_dir  = $upload_dir['basedir'] . '/' . self::CACHE_DIR_NAME . '/dynamic';
			$cache_url  = $upload_dir['baseurl'] . '/' . self::CACHE_DIR_NAME . '/dynamic';
		}

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

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

			// Add .htaccess for security.
			self::create_htaccess( $cache_dir );
		}

		return array(
			'dir' => trailingslashit( $cache_dir ),
			'url' => trailingslashit( $cache_url ),
		);
	}

	/**
	 * Create cache subdirectories.
	 *
	 * Creates organized folders for different cache types.
	 *
	 * @since 2.0.0
	 *
	 * @return void
	 */
	private static function create_cache_subdirectories() {
		foreach ( self::$subdirs as $subdir ) {
			$dir = self::$cacheFolder . $subdir;
			if ( ! is_dir( $dir ) ) {
				wp_mkdir_p( $dir );
			}
		}
	}

	/**
	 * Migrate from legacy cache location.
	 *
	 * @since 2.0.0
	 *
	 * @return void
	 */
	private static function migrate_legacy_cache() {
		// Check if migration already done.
		if ( get_option( 'essb_dynamic_cache_migrated', false ) ) {
			return;
		}

		// Check for legacy cache in uploads directory.
		$upload_dir   = wp_upload_dir();
		$legacy_cache = $upload_dir['basedir'] . '/' . self::LEGACY_CACHE_DIR_NAME;

		if ( is_dir( $legacy_cache ) ) {
			// Remove legacy cache completely.
			self::remove_directory_recursive( $legacy_cache );
		}

		// Mark migration as complete.
		update_option( 'essb_dynamic_cache_migrated', true, false );
	}

	/**
	 * 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 Dynamic Cache
<IfModule mod_expires.c>
ExpiresActive On
ExpiresByType text/css \"access plus 1 year\"
ExpiresByType application/javascript \"access plus 1 year\"
ExpiresByType text/html \"access plus 1 hour\"
</IfModule>

<IfModule mod_headers.c>
Header set Cache-Control \"public, max-age=3600\"
</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 (button HTML).
	 *
	 * @since 1.0.0
	 * @since 2.0.0 Updated to use buttons subfolder.
	 *
	 * @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 . self::$subdirs['buttons'] . '/' . $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 in cache (CSS/JS file).
	 *
	 * @since 1.0.0
	 * @since 2.0.0 Updated to use type-specific subfolders.
	 *
	 * @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 );

		// Determine subfolder based on type.
		$subfolder = isset( self::$subdirs[ $type ] ) ? self::$subdirs[ $type ] : self::$subdirs['data'];
		$filename  = self::$cacheFolder . $subfolder . '/' . $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 (button HTML).
	 *
	 * @since 1.0.0
	 * @since 2.0.0 Updated to use buttons subfolder.
	 *
	 * @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 . self::$subdirs['buttons'] . '/' . $id . '_cache.txt';

		if ( file_exists( $filename ) ) {
			$age = ( time() - filemtime( $filename ) );

			if ( $age < self::$cacheTime ) {
				// phpcs:ignore WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents
				$data = @file_get_contents( $filename );
				return $data ? $data : '';
			}
		}

		return '';
	}

	/**
	 * Retrieve resource URL from cache (CSS/JS file).
	 *
	 * Returns URL if cache exists and is valid.
	 *
	 * @since 1.0.0
	 * @since 2.0.0 Updated to use type-specific subfolders.
	 *
	 * @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 );

		// Determine subfolder based on type.
		$subfolder = isset( self::$subdirs[ $type ] ) ? self::$subdirs[ $type ] : self::$subdirs['data'];
		$filename  = self::$cacheFolder . $subfolder . '/' . $id . '.' . $type;

		if ( file_exists( $filename ) ) {
			$age = ( time() - filemtime( $filename ) );

			if ( $age < self::$cacheTime ) {
				return self::$cacheURL . $subfolder . '/' . $id . '.' . $type;
			}
		}

		return '';
	}

	/**
	 * Flush entire cache.
	 *
	 * Removes all cached files and folders.
	 *
	 * @since 1.0.0
	 * @since 2.0.0 Updated for new structure.
	 *
	 * @return bool True on success, false on failure.
	 */
	public static function flush() {
		if ( ! self::$isActive || empty( self::$cacheFolder ) ) {
			return false;
		}

		$base_path = self::$cacheFolder;

		if ( is_dir( $base_path ) ) {
			return self::remove_directory_recursive( $base_path, false );
		}

		return false;
	}

	/**
	 * Flush single cache entry.
	 *
	 * @since 1.0.0
	 * @since 2.0.0 Updated to use buttons subfolder.
	 *
	 * @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 . self::$subdirs['buttons'] . '/' . $id . '_cache.txt';

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

		return false;
	}

	/**
	 * Flush cache by type.
	 *
	 * Removes all cache files of a specific type.
	 *
	 * @since 2.0.0
	 *
	 * @param string $type Cache type ('buttons', 'css', 'js', 'data').
	 * @return bool True on success, false on failure.
	 */
	public static function flush_by_type( $type ) {
		if ( ! self::$isActive || ! isset( self::$subdirs[ $type ] ) ) {
			return false;
		}

		$type_dir = self::$cacheFolder . self::$subdirs[ $type ];

		if ( is_dir( $type_dir ) ) {
			// Remove all files in directory but keep directory.
			$files = glob( $type_dir . '/*' );
			if ( is_array( $files ) ) {
				foreach ( $files as $file ) {
					if ( is_file( $file ) ) {
						// phpcs:ignore WordPress.WP.AlternativeFunctions.unlink_unlink
						@unlink( $file );
					}
				}
			}
			return true;
		}

		return false;
	}

	/**
	 * Recursively remove directory and contents.
	 *
	 * @since 1.0.0
	 * @since 2.0.0 Added option to preserve root directory.
	 *
	 * @param string $directory     Directory path to remove.
	 * @param bool   $remove_parent Whether to remove the parent directory itself.
	 * @return bool True on success, false on failure.
	 */
	public static function recursiveRemoveDirectory( $directory, $remove_parent = true ) {
		if ( ! is_dir( $directory ) ) {
			return false;
		}

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

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

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

		// Remove parent directory if requested.
		if ( $remove_parent ) {
			// phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_rmdir
			return @rmdir( $directory );
		}

		return true;
	}

	/**
	 * Recursively remove directory (legacy alias).
	 *
	 * @since 1.0.0
	 * @deprecated 2.0.0 Use recursiveRemoveDirectory() instead.
	 *
	 * @param string $directory Directory path to remove.
	 * @return bool True on success, false on failure.
	 */
	private static function remove_directory_recursive( $directory ) {
		return self::recursiveRemoveDirectory( $directory, true );
	}

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

		$stats = array(
			'active'   => true,
			'base_dir' => self::$cacheFolder,
			'base_url' => self::$cacheURL,
			'mode'     => self::$cache_mode,
			'types'    => array(),
		);

		$total_files = 0;
		$total_size  = 0;

		foreach ( self::$subdirs as $type => $subdir ) {
			$type_dir = self::$cacheFolder . $subdir;
			$files    = glob( $type_dir . '/*' );
			$count    = is_array( $files ) ? count( $files ) : 0;
			$size     = 0;

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

			$stats['types'][ $type ] = array(
				'files' => $count,
				'size'  => size_format( $size, 2 ),
			);

			$total_files += $count;
			$total_size  += $size;
		}

		$stats['total_files'] = $total_files;
		$stats['total_size']  = size_format( $total_size, 2 );

		return $stats;
	}
}

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

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

/**
 * Activate dynamic cache.
 *
 * Modern function wrapper for cache activation.
 *
 * @since 2.0.0
 *
 * @param string $mode Cache mode: 'buttons', 'resource', or 'full'.
 * @return bool True on success, false on failure.
 */
function essb_activate_dynamic_cache( $mode = 'full' ) {
	return ESSB_Dynamic_Cache::activate( $mode );
}

/**
 * Store button HTML in cache.
 *
 * @since 2.0.0
 *
 * @param string $key  Cache key.
 * @param string $data HTML content to cache.
 * @return bool True on success, false on failure.
 */
function essb_cache_put( $key, $data ) {
	return ESSB_Dynamic_Cache::put( $key, $data );
}

/**
 * Retrieve button HTML from cache.
 *
 * @since 2.0.0
 *
 * @param string $key Cache key.
 * @return string Cached HTML or empty string.
 */
function essb_cache_get( $key ) {
	return ESSB_Dynamic_Cache::get( $key );
}

/**
 * Store resource file in 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_cache_put_resource( $key, $data, $type = 'css' ) {
	return ESSB_Dynamic_Cache::put_resource( $key, $data, $type );
}

/**
 * Get resource URL from 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_cache_get_resource( $key, $type = 'css' ) {
	return ESSB_Dynamic_Cache::get_resource( $key, $type );
}

/**
 * Flush entire dynamic cache.
 *
 * @since 2.0.0
 *
 * @return bool True on success, false on failure.
 */
function essb_flush_dynamic_cache() {
	return ESSB_Dynamic_Cache::flush();
}

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

/**
 * Flush cache by type.
 *
 * @since 2.0.0
 *
 * @param string $type Cache type ('buttons', 'css', 'js', 'data').
 * @return bool True on success, false on failure.
 */
function essb_flush_cache_by_type( $type ) {
	return ESSB_Dynamic_Cache::flush_by_type( $type );
}

/**
 * Get dynamic cache statistics.
 *
 * @since 2.0.0
 *
 * @return array Cache statistics including file counts and sizes.
 */
function essb_get_dynamic_cache_stats() {
	return ESSB_Dynamic_Cache::get_cache_stats();
}

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

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

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