<?php
/**
 * Frontend class.
 *
 * Handles:
 *  - Auto-appending related posts to single post content
 *  - [igit_related_posts] shortcode
 *  - Gutenberg block server-side render callback
 *  - Enqueuing frontend stylesheet (and async-loader JS when load_async=true)
 *  - AJAX endpoint for async loading
 *
 * @package IGIT_RPWT
 */

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

class IGIT_RPWT_Frontend
{

	/**
	 * Action name for the async AJAX endpoint.
	 */
	const AJAX_ACTION = 'igit_rpwt_load';

	public function __construct()
	{
		add_action('wp_enqueue_scripts', array($this, 'enqueue_assets'));
		add_shortcode('igit_related_posts', array($this, 'shortcode'));

		if (IGIT_RPWT_Settings::get('auto_show')) {
			add_filter('the_content', array($this, 'append_to_content'));
		}
	}

	// ── Asset enqueuing ──────────────────────────────────────────────────────

	/**
	 * Enqueue the frontend stylesheet and (conditionally) the loader JS.
	 *
	 * frontend.js is enqueued when either async loading or click tracking is on.
	 */
	public function enqueue_assets()
	{
		wp_enqueue_style(
			'igit-rpwt-frontend',
			IGIT_RPWT_PLUGIN_URL . 'assets/css/frontend.css',
			array(),
			IGIT_RPWT_VERSION
		);

		// Inject grid column counts as CSS custom properties for all three breakpoints.
		$cols_d = max(2, min(6, absint(IGIT_RPWT_Settings::get('grid_columns_desktop'))));
		$cols_t = max(1, min(3, absint(IGIT_RPWT_Settings::get('grid_columns_tablet'))));
		$cols_m = max(1, min(2, absint(IGIT_RPWT_Settings::get('grid_columns_mobile'))));
		$grid_css = sprintf(
			'.igit-rpwt--grid .igit-rpwt__items{--igit-cols:%d}'
			. '@media(max-width:768px){.igit-rpwt--grid .igit-rpwt__items{--igit-cols:%d}}'
			. '@media(max-width:480px){.igit-rpwt--grid .igit-rpwt__items{--igit-cols:%d}}',
			$cols_d, $cols_t, $cols_m
		);
		wp_add_inline_style('igit-rpwt-frontend', $grid_css);

		// load_async is a Pro-only feature — check the DB value only when Pro is
		// physically active. This check runs on wp_enqueue_scripts, which fires
		// after all plugins_loaded callbacks, so IGIT_RPWT_PRO is reliable here.
		$load_async = (bool)IGIT_RPWT_Settings::get('load_async') && igit_rpwt_is_pro();
		// click_tracking is Pro-only — gate it the same way as load_async above.
		$click_tracking = (bool) IGIT_RPWT_Settings::get( 'enable_click_tracking' ) && igit_rpwt_is_pro();
		$is_carousel = 'carousel' === IGIT_RPWT_Settings::get('layout');

		// Enqueue carousel.js when layout is carousel OR when async loading is on.
		// Async AJAX can return carousel markup, so window.igitRpwtCarousel must be
		// available before the AJAX response arrives.
		if ($is_carousel || $load_async) {
			wp_enqueue_script(
				'igit-rpwt-carousel',
				IGIT_RPWT_PLUGIN_URL . 'assets/js/carousel.js',
				array(),
				IGIT_RPWT_VERSION,
				true
			);
		}

		if ($load_async || $click_tracking) {
			wp_enqueue_script(
				'igit-rpwt-frontend',
				IGIT_RPWT_PLUGIN_URL . 'assets/js/frontend.js',
				array(),
				IGIT_RPWT_VERSION,
				true
			);

			$js_data = array(
				'ajaxUrl' => admin_url('admin-ajax.php'),
			);

			if ($load_async) {
				$js_data['action'] = self::AJAX_ACTION;
				$js_data['nonce'] = wp_create_nonce(self::AJAX_ACTION);
			}

			if ($click_tracking && igit_rpwt_is_pro()) {
				$js_data['trackAction'] = IGIT_RPWT_Analytics::AJAX_ACTION;
				$js_data['trackNonce'] = wp_create_nonce(IGIT_RPWT_Analytics::NONCE_ACTION);
			}

			wp_localize_script('igit-rpwt-frontend', 'igitRpwt', $js_data);
		}
	}

	// ── Content output ───────────────────────────────────────────────────────

	/**
	 * Append related posts to single post content.
	 *
	 * @param string $content
	 * @return string
	 */
	public function append_to_content($content)
	{
		// Guard against re-entrant calls: when show_excerpt is on, WordPress's
		// wp_trim_excerpt() applies the_content filter to auto-generate excerpts
		// for related posts with no manual excerpt — which recurses back here and
		// exhausts memory. The static flag breaks that cycle.
		static $processing = false;
		if ($processing) {
			return $content;
		}

		// Check against the configured post types, not the hardcoded 'post'.
		$post_types = (array)IGIT_RPWT_Settings::get('post_types');
		if (empty($post_types)) {
			$post_types = array('post');
		}
		if (!is_singular($post_types) || !in_the_loop() || !is_main_query()) {
			return $content;
		}

		$processing = true;
		$result = $content . $this->output(get_the_ID());
		$processing = false;
		return $result;
	}

	/**
	 * [igit_related_posts] shortcode.
	 *
	 * Attributes:
	 *  count  – number of posts (default: setting value)
	 *  layout – 'grid' or 'list' (default: setting value)
	 *
	 * @param array $atts
	 * @return string HTML
	 */
	public function shortcode($atts)
	{
		$atts = shortcode_atts(
			array(
			'count' => IGIT_RPWT_Settings::get('post_count'),
			'layout' => IGIT_RPWT_Settings::get('layout'),
		),
			$atts,
			'igit_related_posts'
		);

		$post_id = get_the_ID();
		if (!$post_id) {
			return '';
		}

		return $this->output($post_id, array(
			'post_count' => absint($atts['count']),
			'layout' => sanitize_key($atts['layout']),
		));
	}

	/**
	 * Gutenberg block server-side render callback.
	 *
	 * @param array $block_attributes
	 * @return string HTML
	 */
	public static function render_block($block_attributes)
	{
		$post_id = get_the_ID();
		if (!$post_id) {
			return '';
		}

		$args = array(
			'post_count' => isset($block_attributes['postCount']) ? absint($block_attributes['postCount']) : null,
			'layout' => isset($block_attributes['layout']) ? sanitize_key($block_attributes['layout']) : null,
		);

		// Blocks are rendered server-side into full HTML; async is not applicable here.
		return IGIT_RPWT_Renderer::render($post_id, $args);
	}

	// ── Async / sync dispatch ────────────────────────────────────────────────

	/**
	 * Return either a synchronous HTML string or an async placeholder, depending
	 * on the load_async setting.
	 *
	 * @param int   $post_id
	 * @param array $args
	 * @return string
	 */
	private function output($post_id, array $args = array())
	{
		// Async loading is Pro-only: fall back to sync render when Pro is not active.
		// This runs on the_content, safely after all plugins_loaded callbacks.
		if (!IGIT_RPWT_Settings::get('load_async') || !igit_rpwt_is_pro()) {
			return IGIT_RPWT_Renderer::render($post_id, $args);
		}

		// Async mode: render a lightweight placeholder. The JS loader will
		// replace it with real content via AJAX after the page has loaded.
		$encoded_args = wp_json_encode($args);
		return sprintf(
			'<div class="igit-rpwt-placeholder" data-post-id="%d" data-args="%s" aria-live="polite"><span class="igit-rpwt-loading">%s</span></div>',
			absint($post_id),
			esc_attr($encoded_args),
			esc_html__('Loading related posts…', 'igit-related-posts-with-thumb-images-after-posts')
		);
	}

	// ── AJAX endpoint ────────────────────────────────────────────────────────

	/**
	 * Static entry-point called by wp_ajax_* hooks registered in class-plugin.php.
	 *
	 * wp_ajax_* fires inside wp-admin/admin-ajax.php where is_admin()=true, so
	 * IGIT_RPWT_Frontend is never instantiated — the handler must be static.
	 */
	public static function ajax_load_static()
	{
		self::handle_ajax_load();
	}

	/**
	 * Handle the async AJAX request.
	 *
	 * Expects POST fields: post_id, args (JSON), nonce.
	 */
	public function ajax_load()
	{
		self::handle_ajax_load();
	}

	/**
	 * Shared implementation for both static and instance AJAX entry-points.
	 */
	private static function handle_ajax_load()
	{
		check_ajax_referer(self::AJAX_ACTION, 'nonce');

		$post_id = isset($_POST['post_id']) ? absint($_POST['post_id']) : 0;
		if (!$post_id || !get_post($post_id)) {
			wp_send_json_error(array('message' => 'Invalid post.'), 400);
		}

		$args = array();
		if (!empty($_POST['args'])) {
			$decoded = json_decode(wp_unslash($_POST['args']), true); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- keys are whitelisted in the $allowed array below.
			if (is_array($decoded)) {
				// Whitelist allowed override keys to prevent injection.
				$allowed = array('post_count', 'layout');
				foreach ($allowed as $key) {
					if (isset($decoded[$key])) {
						$args[$key] = $decoded[$key];
					}
				}
			}
		}

		$html = IGIT_RPWT_Renderer::render($post_id, $args);
		wp_send_json_success(array('html' => $html));
	}
}