<?php
/**
 * Cache module — Audit recommender
 *
 * Maps Lighthouse audit IDs (used by both PSI and our internal audit) to
 * actionable recommendations. Each entry classifies the fix as:
 *
 *   - 'plugin'  : Toggling an option in this plugin will resolve / improve it.
 *   - 'theme'   : Theme or content change required (we explain what).
 *   - 'hosting' : Server / hosting tweak (gzip, HTTP/2, TTFB, …).
 *   - 'mixed'   : Plugin can help but the root cause is theme/hosting.
 *
 * @package BlaminhorEssentials
 */

if ( ! defined( 'ABSPATH' ) ) {
	exit;
}

class Blaminhor_Essentials_Cache_Audit_Recommender {

	/** @var Blaminhor_Essentials_Module_Cache */
	private $parent;

	public function __construct( Blaminhor_Essentials_Module_Cache $parent ) {
		$this->parent = $parent;
	}

	/**
	 * Build a list of recommendations from raw audits.
	 *
	 * @param array $audits Audit list (PSI or internal — same shape).
	 * @return array<int, array{
	 *   id: string, title: string, description: string,
	 *   category: string, action: array|null,
	 * }>
	 */
	public function recommend( array $audits ) {
		$settings = $this->parent->get_effective_settings();
		$out      = array();
		foreach ( $audits as $audit ) {
			if ( ! isset( $audit['id'] ) ) {
				continue;
			}
			$mapping = $this->map_audit( $audit['id'] );
			// A recommendation is "resolved" when the underlying toggle has
			// been turned on since the audit ran — useful when re-rendering a
			// stored audit so the user sees what they have already actioned.
			$is_resolved = false;
			if ( ! empty( $mapping['action']['field'] ) ) {
				$field       = (string) $mapping['action']['field'];
				$is_resolved = ! empty( $settings[ $field ] ?? null );
			}
			$out[] = array(
				'id'          => $audit['id'],
				'title'       => isset( $audit['title'] ) ? (string) $audit['title'] : $audit['id'],
				'description' => isset( $audit['description'] ) ? (string) $audit['description'] : '',
				'displayValue'=> isset( $audit['displayValue'] ) ? (string) $audit['displayValue'] : '',
				'urls'        => isset( $audit['urls'] ) ? (array) $audit['urls'] : array(),
				'score'       => isset( $audit['score'] ) ? $audit['score'] : null,
				'category'    => $mapping['category'],
				'action'      => $mapping['action'],
				'guidance'    => $mapping['guidance'],
				'is_resolved' => $is_resolved,
			);
		}
		return $out;
	}

	/**
	 * Audit-id → recommendation mapping.
	 *
	 * @param string $audit_id
	 * @return array{category: string, action: array|null, guidance: string}
	 */
	private function map_audit( $audit_id ) {
		$tab_url = admin_url( 'admin.php?page=blaminhor-essentials-cache' );

		// Build the hash so the destination tab can scroll to and highlight
		// the exact setting row. Format: "#tab[:field]" — admin.js parses this.
		$build_url = function ( $tab_anchor, $field ) use ( $tab_url ) {
			return $tab_url . '#' . $tab_anchor . ( '' !== (string) $field ? ':' . $field : '' );
		};

		// Read the *effective* settings (DB values merged with defaults) so
		// that toggles whose default is `true` and that have never been
		// explicitly saved still register as "on". Using get_setting() alone
		// misses those because load_settings() does not merge defaults.
		$settings = $this->parent->get_effective_settings();
		$is_on    = function ( $field ) use ( $settings ) {
			return '' !== (string) $field && ! empty( $settings[ $field ] ?? null );
		};

		$already_on_message = __( 'This setting is already enabled but the symptom persists. The cure is content- or server-side: check your theme, third-party plugins, or hosting configuration.', 'blaminhor-essentials' );

		$plugin = function ( $tab_anchor, $field, $human ) use ( $build_url, $is_on, $already_on_message ) {
			// Demote to a no-action guidance card when the setting is already
			// toggled on — the user shouldn't be sent to a checkbox they can't
			// "fix" by clicking. The real cause is theme / hosting / content.
			if ( $is_on( $field ) ) {
				return array( 'category' => 'theme', 'action' => null, 'guidance' => $already_on_message );
			}
			return array(
				'category' => 'plugin',
				'action'   => array(
					'label' => __( 'Open the relevant setting', 'blaminhor-essentials' ),
					'url'   => $build_url( $tab_anchor, $field ),
					'field' => $field,
					'human' => $human,
				),
				'guidance' => '',
			);
		};

		$theme = function ( $message ) {
			return array( 'category' => 'theme', 'action' => null, 'guidance' => $message );
		};

		$hosting = function ( $message ) {
			return array( 'category' => 'hosting', 'action' => null, 'guidance' => $message );
		};

		$mixed = function ( $tab_anchor, $field, $human, $message ) use ( $build_url, $is_on ) {
			// Same idea as $plugin above: if the toggle is already on, drop the
			// redundant button and let the existing $message — which is already
			// about the external cause — stand on its own as a hosting note.
			if ( $is_on( $field ) ) {
				return array( 'category' => 'hosting', 'action' => null, 'guidance' => $message );
			}
			return array(
				'category' => 'mixed',
				'action'   => array(
					'label' => __( 'Open the relevant setting', 'blaminhor-essentials' ),
					'url'   => $build_url( $tab_anchor, $field ),
					'field' => $field,
					'human' => $human,
				),
				'guidance' => $message,
			);
		};

		switch ( $audit_id ) {

			case 'unminified-css':
				return $plugin( 'files', 'minify_css', __( 'Try Minify CSS in the Files tab', 'blaminhor-essentials' ) );

			case 'unminified-javascript':
				return $plugin( 'files', 'minify_js', __( 'JS minification ships in an upcoming update', 'blaminhor-essentials' ) );

			case 'unused-css-rules':
			case 'unused-javascript':
				return $mixed(
					'files',
					'rucss_enabled',
					__( 'Try Remove Unused CSS in the Files tab', 'blaminhor-essentials' ),
					__( 'Page builders often ship more CSS than they use; auditing dependencies and disabling unused widgets is the only complete fix.', 'blaminhor-essentials' )
				);

			case 'render-blocking-resources':
				return $plugin( 'files', 'defer_js', __( 'Try Defer JS in the Files tab — Critical CSS amplifies the gain', 'blaminhor-essentials' ) );

			case 'modern-image-formats':
			case 'uses-webp-images':
			case 'uses-optimized-images':
				return $theme( __( 'WordPress 6.1+ supports WebP/AVIF natively. The fix is content-side: re-export images in WebP/AVIF or use a dedicated converter plugin (Imagify, ShortPixel, etc.).', 'blaminhor-essentials' ) );

			case 'uses-text-compression':
				return $mixed(
					'page-cache',
					'gzip_enabled',
					__( 'Try GZIP in the Page Cache tab (Apache / LiteSpeed only)', 'blaminhor-essentials' ),
					__( 'On Nginx or a managed host, your provider needs to enable gzip or brotli at the server level.', 'blaminhor-essentials' )
				);

			case 'uses-long-cache-ttl':
				return $mixed(
					'page-cache',
					'browser_cache_enabled',
					__( 'Try Browser cache in the Page Cache tab', 'blaminhor-essentials' ),
					__( 'Apache / LiteSpeed only. On Nginx or a managed host, the expires headers belong in your server config.', 'blaminhor-essentials' )
				);

			case 'uses-rel-preconnect':
				return $plugin( 'preload', 'preconnect_domains', __( 'You can add the busiest third-party origins to Preconnect (Preload tab)', 'blaminhor-essentials' ) );

			case 'uses-rel-preload':
			case 'preload-lcp-image':
				return $plugin( 'preload', 'preload_fonts', __( 'You can preload critical fonts and the LCP image (Preload tab)', 'blaminhor-essentials' ) );

			case 'font-display':
				return $plugin( 'files', 'fonts_display_swap', __( 'Try Google Fonts display=swap in the Files tab', 'blaminhor-essentials' ) );

			case 'offscreen-images':
				return $plugin( 'media', 'lazy_load_images', __( 'Try Lazy load images in the Media tab', 'blaminhor-essentials' ) );

			case 'unsized-images':
				return $theme( __( 'WordPress automatically sets width/height on images served via the Media library since 5.5. Check that no plugin / theme strips them. Manual <img> tags in your content need explicit dimensions.', 'blaminhor-essentials' ) );

			case 'duplicated-javascript':
				return $theme( __( 'Multiple plugins shipping the same library (jQuery vendored twice, etc.). The cure is identifying the offender and disabling its bundled copy.', 'blaminhor-essentials' ) );

			case 'legacy-javascript':
				return $theme( __( 'Polyfills shipped to modern browsers that no longer need them. Theme/builder concern.', 'blaminhor-essentials' ) );

			case 'efficient-animated-content':
				return $theme( __( 'Replace animated GIFs with looping MP4/WebM. The plugin does not transcode video.', 'blaminhor-essentials' ) );

			case 'redirects':
				return $theme( __( 'Internal links pointing to URLs that redirect. Update content links so they go directly to the canonical URL.', 'blaminhor-essentials' ) );

			case 'server-response-time':
				return $mixed(
					'page-cache',
					'page_cache_enabled',
					__( 'Try Page cache — typically slashes TTFB on uncached requests', 'blaminhor-essentials' ),
					__( 'Even with caching, a very slow TTFB on uncached requests usually points to your hosting CPU, RAM or database as the bottleneck.', 'blaminhor-essentials' )
				);

			case 'third-party-summary':
			case 'third-party-facades':
				return $mixed(
					'media',
					'youtube_facade',
					__( 'Try YouTube facade in the Media tab', 'blaminhor-essentials' ),
					__( 'Beyond YouTube embeds, removing or facade-loading marketing pixels is usually the most durable fix — worth a chat with whoever installed Hotjar, Pinterest or Facebook tags.', 'blaminhor-essentials' )
				);

			case 'dom-size':
				return $theme( __( 'Heavy markup (~1500+ nodes) is usually caused by page builders rendering many wrappers per block. The plugin cannot reduce DOM size.', 'blaminhor-essentials' ) );

			case 'critical-request-chains':
			case 'bootup-time':
			case 'mainthread-work-breakdown':
				return $mixed(
					'files',
					'delay_js',
					__( 'Try Delay JS in the Files tab', 'blaminhor-essentials' ),
					__( 'For very heavy pages, the most durable fix is removing scripts you do not actually need — a tag-manager audit usually pays off.', 'blaminhor-essentials' )
				);

			case 'bf-cache':
				return $theme( __( 'Pages with `Cache-Control: no-store` headers OR an `unload` event listener cannot be back-forward-cached. Audit theme JS for unload handlers.', 'blaminhor-essentials' ) );

			case 'lcp-lazy-loaded':
				return $plugin( 'media', 'lazy_load_images', __( 'The plugin auto-marks the first image as eager (Media tab)', 'blaminhor-essentials' ) );

			case 'non-composited-animations':
				return $theme( __( 'Animations using top/left instead of transform. Theme/CSS concern.', 'blaminhor-essentials' ) );

			case 'total-byte-weight':
				return $mixed(
					'media',
					'lazy_load_images',
					__( 'Lazy load + critical CSS + delay JS reduce the over-the-wire payload', 'blaminhor-essentials' ),
					__( 'Compress / resize source images and remove unused plugins for the largest gains.', 'blaminhor-essentials' )
				);

			case 'uses-responsive-images':
				return $theme( __( 'WP serves srcset by default for media-library images. Hard-coded <img> in your content need explicit srcsets.', 'blaminhor-essentials' ) );

			default:
				return array(
					'category' => 'theme',
					'action'   => null,
					'guidance' => __( 'No automated mapping yet — review the audit description.', 'blaminhor-essentials' ),
				);
		}
	}
}
