<?php
/**
 * Manifest Reader - Vite manifest parsing and asset path resolution.
 *
 * @package B3WVS\VariationSwatches\Assets
 */

namespace B3WVS\VariationSwatches\Assets;

defined( 'ABSPATH' ) || exit;

/**
 * ManifestReader Class
 *
 * Handles Vite manifest parsing and provides asset file paths with cache-busting.
 */
class ManifestReader {

	/**
	 * Cached manifest data.
	 *
	 * @var array|null
	 */
	private static $manifest = null;

	/**
	 * Plugin directory path.
	 *
	 * @var string
	 */
	private $plugin_path;

	/**
	 * Constructor.
	 *
	 * @param string $plugin_path Plugin directory path.
	 */
	public function __construct( $plugin_path ) {
		$this->plugin_path = $plugin_path;
	}

	/**
	 * Get manifest data.
	 *
	 * @return array
	 */
	public function get_manifest() {
		if ( ! is_null( self::$manifest ) ) {
			return self::$manifest;
		}

		$manifest_paths = array(
			// Preferred: non-hidden manifest path (survives deploy processors that strip dot-directories).
			$this->plugin_path . 'dist/manifest.json',
			$this->plugin_path . 'dist/manifest.free.json',
			// Backward compatibility for existing local builds.
			$this->plugin_path . 'dist/.vite/manifest.json',
		);
		$manifest_path  = '';

		foreach ( $manifest_paths as $candidate ) {
			if ( file_exists( $candidate ) && is_readable( $candidate ) ) {
				$manifest_path = $candidate;
				break;
			}
		}

		if ( '' === $manifest_path ) {
			self::$manifest = array();
			return self::$manifest;
		}

		// phpcs:ignore WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents -- Reading local manifest file, not a remote URL.
		$content = file_get_contents( $manifest_path );
		if ( false === $content ) {
			self::$manifest = array();
			return self::$manifest;
		}

		// Trim UTF-8 BOM if present before json_decode().
		$content = preg_replace( '/^\xEF\xBB\xBF/', '', $content );
		if ( ! is_string( $content ) ) {
			self::$manifest = array();
			return self::$manifest;
		}

		self::$manifest = json_decode( $content, true );
		if ( ! is_array( self::$manifest ) ) {
			self::$manifest = array();
		}

		return self::$manifest;
	}

	/**
	 * Check if an entry exists in the manifest.
	 *
	 * @param string $entry Entry point (e.g., 'src/frontend/main.tsx').
	 * @return bool
	 */
	public function has_entry( $entry ) {
		$manifest = $this->get_manifest();
		return ! empty( $manifest ) && isset( $manifest[ $entry ] );
	}

	/**
	 * Get asset data for an entry point.
	 *
	 * @param string $entry Entry point in manifest.
	 * @return array|null Asset data or null if not found.
	 */
	public function get_entry( $entry ) {
		$manifest = $this->get_manifest();

		if ( empty( $manifest ) || ! isset( $manifest[ $entry ] ) ) {
			return null;
		}

		return $manifest[ $entry ];
	}

	/**
	 * Get the JS file path for an entry.
	 *
	 * @param string $entry Entry point.
	 * @return string|null File path or null.
	 */
	public function get_js_file( $entry ) {
		$asset_data = $this->get_entry( $entry );
		return $asset_data ? $asset_data['file'] : null;
	}

	/**
	 * Get CSS files for an entry.
	 *
	 * @param string $entry Entry point.
	 * @return array CSS file paths.
	 */
	public function get_css_files( $entry ) {
		$asset_data = $this->get_entry( $entry );
		return ( $asset_data && isset( $asset_data['css'] ) ) ? $asset_data['css'] : array();
	}

	/**
	 * Get file version based on modification time for cache-busting.
	 *
	 * @param string $file_path Absolute file path.
	 * @return string|null Version string or null.
	 */
	public function get_file_version( $file_path ) {
		return file_exists( $file_path ) ? (string) filemtime( $file_path ) : null;
	}

	/**
	 * Check if manifest is empty (development mode fallback).
	 *
	 * @return bool
	 */
	public function is_empty() {
		return empty( $this->get_manifest() );
	}

	/**
	 * Reset cached manifest (useful for testing).
	 */
	public static function reset_cache() {
		self::$manifest = null;
	}
}
