<?php
/**
 * Base class for page-specific recommendations.
 *
 * @package PRECO
 */

namespace PRECO\Includes\Frontend\Core;

use PRECO\Includes\Frontend\Core\Page\LocationRenderer;

defined( 'ABSPATH' ) || exit;

/**
 * Base OOP handler for rendering recommendations at configured locations.
 */
abstract class PageRecommendationsBase {

	/**
	 * Recommendations config.
	 *
	 * @var RecommendationsConfig
	 */
	protected $config;

	/**
	 * Recommendations engine.
	 *
	 * @var RecommendationsEngine
	 */
	protected $engine;

	/**
	 * Recommendations renderer.
	 *
	 * @var RecommendationsRenderer
	 */
	protected $renderer;

	/**
	 * Location renderer.
	 *
	 * @var LocationRenderer
	 */
	protected $location_renderer;

	/**
	 * Builder preview renderer.
	 *
	 * @var PreviewRenderer
	 */
	protected $preview_renderer;

	/**
	 * Locations already rendered on this request, keyed "page|location".
	 *
	 * A block theme (Twenty Twenty-Five and friends) renders the archive via
	 * the woocommerce/product-collection block AND still fires the legacy
	 * woocommerce_before/after_shop_loop actions for back-compat, so both
	 * injection paths run and the widget would appear twice. First one in
	 * wins; the rest are no-ops.
	 *
	 * @var array<string, bool>
	 */
	private $rendered_locations = array();

	/**
	 * Constructor.
	 */
	public function __construct() {
		$this->config            = new RecommendationsConfig();
		$this->engine            = new RecommendationsEngine();
		$this->renderer          = new RecommendationsRenderer();
		$this->location_renderer = new LocationRenderer( $this->config, $this->engine, $this->renderer );
		$this->preview_renderer  = new PreviewRenderer( $this->renderer, $this->engine );

		add_action( 'wp', array( $this, 'register_hooks' ), 20 );
	}

	/**
	 * Page key in option structure.
	 *
	 * @return string
	 */
	abstract protected function page_key();

	/**
	 * Is the current request the relevant page?
	 *
	 * @return bool
	 */
	abstract protected function is_active_page();

	/**
	 * Map of location key => hook definition.
	 * Each item: ['hook' => string, 'priority' => int, 'accepted_args' => int]
	 *
	 * @return array
	 */
	abstract protected function locations();

	/**
	 * Register WP hooks for all locations.
	 *
	 * @return void
	 */
	public function register_hooks() {
		if ( ! $this->is_active_page() ) {
			return;
		}
		if ( ! $this->is_page_enabled_for_request() ) {
			return;
		}

		foreach ( $this->locations() as $location_key => $def ) {
			$hook          = isset( $def['hook'] ) ? (string) $def['hook'] : '';
			$priority      = isset( $def['priority'] ) ? (int) $def['priority'] : 10;
			$accepted_args = isset( $def['accepted_args'] ) ? (int) $def['accepted_args'] : 0;
			if ( ! $hook ) {
				continue;
			}

			add_action(
				$hook,
				function ( ...$args ) use ( $location_key ) {
					$this->render_location( $location_key, $args );
				},
				$priority,
				max( 0, $accepted_args )
			);
		}
	}

	/**
	 * Render recommendations for a location.
	 *
	 * @param string $location_key Location key.
	 * @param array  $hook_args Args passed by hook.
	 * @return void
	 */
	protected function render_location( $location_key, $hook_args = array() ) {
		$page_key = $this->page_key();

		$guard = $page_key . '||' . $location_key;
		if ( isset( $this->rendered_locations[ $guard ] ) ) {
			return;
		}
		$this->rendered_locations[ $guard ] = true;

		// Built for both branches: on a real page load (including the builder's
		// full iframe load/reload of it) is_product()/WC()->cart/
		// get_queried_object() etc. resolve exactly like production, so preview
		// can reuse the same per-page-type context this page already builds for
		// real rendering — see PreviewRenderer::render_location().
		$context = $this->build_context( $location_key, $hook_args );

		if ( PreviewMode::is_active_request() ) {
			$this->preview_renderer->render_location( $page_key, $location_key, $context );
			return;
		}

		$this->location_renderer->render( $page_key, $location_key, $hook_args, $context );
	}

	/**
	 * Build context for rule evaluation.
	 *
	 * @param string $location_key Location key.
	 * @param array  $hook_args Hook args.
	 * @return array
	 */
	protected function build_context( $location_key, $hook_args ) {
		return array(
			'location_key' => (string) $location_key,
		);
	}

	/**
	 * Whether this page's hooks should register for the current request.
	 *
	 * A page a merchant has toggled off should still register its hooks
	 * during a builder preview request, so they can see it before turning it
	 * on — the saved "enabled" setting only governs real storefront traffic.
	 *
	 * @return bool
	 */
	protected function is_page_enabled_for_request() {
		if ( PreviewMode::is_active_request() ) {
			return true;
		}
		return $this->config->is_page_enabled( $this->page_key() );
	}
}
