<?php
/**
 * NotFound recovery page renderer.
 *
 * @package SEOvaultSEO\NotFound
 */

namespace SEOvaultSEO\NotFound;

use SEOvaultSEO\Compatibility\Detector;
use SEOvaultSEO\Core\Options;

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

/**
 * Renders the optional 404 recovery template.
 */
class RecoveryRenderer {

	/**
	 * Run after typical theme `template_include` callbacks (priority 10)
	 * so the recovery template replaces the theme 404 when enabled.
	 *
	 * @var int
	 */
	const TEMPLATE_INCLUDE_PRIORITY = 99;

	/**
	 * Stylesheet handle for the recovery page.
	 *
	 * @var string
	 */
	const STYLE_HANDLE = 'seovaultseo-notfound-recovery';

	/**
	 * Whether the current request is serving the recovery template.
	 *
	 * @var bool
	 */
	private static $serving_recovery = false;

	/**
	 * Register recovery template_include and stylesheet hooks.
	 */
	public static function init() {
		add_filter(
			'template_include',
			array( __CLASS__, 'filter_template_include' ),
			self::TEMPLATE_INCLUDE_PRIORITY
		);
		add_action( 'wp_enqueue_scripts', array( __CLASS__, 'enqueue_assets' ) );
	}

	/**
	 * Whether the recovery template should replace the theme 404.
	 *
	 * Requires an unmatched 404 with fallback mode `theme` and recovery
	 * enabled. Skips admin, AJAX, REST, cron, feeds, and robots requests.
	 * Content mode (`builtin` / `page`) does not affect whether the template
	 * is served — only which body content is rendered inside it.
	 *
	 * @return bool
	 */
	public static function should_serve_recovery() {
		if ( ! Detector::should_output_seo() ) {
			return false;
		}

		if ( self::is_excluded_request_context() ) {
			return false;
		}

		if ( ! is_404() ) {
			return false;
		}

		if ( 'theme' !== Options::get_notfound_fallback_mode() ) {
			return false;
		}

		if ( ! Options::is_notfound_recovery_enabled() ) {
			return false;
		}

		return is_readable( self::get_template_path() );
	}

	/**
	 * Swap in the recovery template on qualifying 404 requests.
	 *
	 * Keeps HTTP 404. Does not write Monitor404 logs here — recovery.php calls
	 * get_header(), which is the classic-theme capture hook, so hits are logged
	 * once like normal theme 404s (FallbackRedirector does not log in theme mode).
	 *
	 * @param string $template Path selected by WordPress / the theme.
	 * @return string
	 */
	public static function filter_template_include( $template ) {
		if ( ! self::should_serve_recovery() ) {
			return $template;
		}

		$path                   = self::get_template_path();
		self::$serving_recovery = true;
		self::ensure_404_status();

		return $path;
	}

	/**
	 * Enqueue recovery CSS only while the recovery template is active.
	 */
	public static function enqueue_assets() {
		if ( ! self::$serving_recovery ) {
			return;
		}

		$path = self::get_stylesheet_path();
		if ( ! is_readable( $path ) ) {
			return;
		}

		wp_enqueue_style(
			self::STYLE_HANDLE,
			self::get_stylesheet_url(),
			array(),
			SEOVAULT_SEO_VERSION
		);
	}

	/**
	 * Whether the recovery template is active for this request.
	 *
	 * @return bool
	 */
	public static function is_serving_recovery() {
		return self::$serving_recovery;
	}

	/**
	 * Reset request-local serving state (tests).
	 */
	public static function reset_serving_state() {
		self::$serving_recovery = false;
	}

	/**
	 * Absolute filesystem path to the recovery template.
	 *
	 * @return string
	 */
	public static function get_template_path() {
		return SEOVAULT_SEO_DIR . 'views/not-found/recovery.php';
	}

	/**
	 * Absolute filesystem path to the recovery stylesheet.
	 *
	 * @return string
	 */
	public static function get_stylesheet_path() {
		return SEOVAULT_SEO_DIR . 'assets/css/not-found-recovery.css';
	}

	/**
	 * Public URL for the recovery stylesheet.
	 *
	 * @return string
	 */
	public static function get_stylesheet_url() {
		return SEOVAULT_SEO_URL . 'assets/css/not-found-recovery.css';
	}

	/**
	 * Render recovery HTML for a structured content array.
	 *
	 * @param array $content Recovery content (see RecoveryContent::get()).
	 */
	public static function render( $content ) {
		$content  = self::normalize_content( $content );
		$colors   = self::colors_for_content( $content );
		$template = self::get_template_path();

		if ( ! is_readable( $template ) ) {
			return;
		}

		if ( ! function_exists( 'seovaultai_render_notfound_recovery' ) ) {
			if ( ! defined( 'SEOVAULTAI_RECOVERY_MANUAL_RENDER' ) ) {
				define( 'SEOVAULTAI_RECOVERY_MANUAL_RENDER', true );
			}
			require_once $template;
		}

		seovaultai_render_notfound_recovery( $content, $colors );
	}

	/**
	 * Keep HTTP status and query flags as a 404 while showing recovery UI.
	 */
	private static function ensure_404_status() {
		global $wp_query;

		if ( isset( $wp_query ) && $wp_query instanceof \WP_Query ) {
			$wp_query->set_404();
		}

		status_header( 404 );
		nocache_headers();
	}

	/**
	 * Request contexts that must never receive the recovery template.
	 *
	 * @return bool
	 */
	private static function is_excluded_request_context() {
		if ( is_admin() ) {
			return true;
		}

		if ( wp_doing_ajax() || wp_doing_cron() ) {
			return true;
		}

		if ( defined( 'REST_REQUEST' ) && REST_REQUEST ) {
			return true;
		}

		if ( function_exists( 'wp_is_json_request' ) && wp_is_json_request() ) {
			return true;
		}

		if ( is_feed() ) {
			return true;
		}

		if ( function_exists( 'is_robots' ) && is_robots() ) {
			return true;
		}

		return false;
	}

	/**
	 * Normalize template content keys and types.
	 *
	 * @param mixed $content Raw content array.
	 * @return array{
	 *     headline:string,
	 *     message:string,
	 *     show_search:bool,
	 *     show_home_cta:bool,
	 *     home_url:string,
	 *     cta_label:string,
	 *     cta_url:string,
	 *     show_recent:bool,
	 *     recent_posts:array<int, array{title:string,url:string}>,
	 *     use_theme_colors:bool,
	 *     accent:string,
	 *     content_mode:string,
	 *     page_body_html:string
	 * }
	 */
	private static function normalize_content( $content ) {
		if ( ! is_array( $content ) ) {
			$content = array();
		}

		$recent_posts = array();
		if ( isset( $content['recent_posts'] ) && is_array( $content['recent_posts'] ) ) {
			foreach ( $content['recent_posts'] as $post ) {
				if ( ! is_array( $post ) ) {
					continue;
				}

				$title = isset( $post['title'] ) && is_string( $post['title'] ) ? $post['title'] : '';
				$url   = isset( $post['url'] ) && is_string( $post['url'] ) ? $post['url'] : '';
				if ( '' === $title || '' === $url ) {
					continue;
				}

				$recent_posts[] = array(
					'title' => $title,
					'url'   => $url,
				);
			}
		}

		$accent = Sanitizer::sanitize_recovery_accent(
			isset( $content['accent'] ) ? $content['accent'] : ''
		);

		$home_url = isset( $content['home_url'] ) && is_string( $content['home_url'] ) ? $content['home_url'] : '';
		if ( '' === $home_url ) {
			$home_url = home_url( '/' );
		}

		$cta_url = isset( $content['cta_url'] ) && is_string( $content['cta_url'] ) ? $content['cta_url'] : '';
		if ( '' === $cta_url ) {
			$cta_url = $home_url;
		}

		$page_body_html = isset( $content['page_body_html'] ) && is_string( $content['page_body_html'] )
			? $content['page_body_html']
			: '';

		return array(
			'headline'         => isset( $content['headline'] ) && is_string( $content['headline'] ) ? $content['headline'] : '',
			'message'          => isset( $content['message'] ) && is_string( $content['message'] ) ? $content['message'] : '',
			'show_search'      => ! empty( $content['show_search'] ),
			'show_home_cta'    => ! empty( $content['show_home_cta'] ),
			'home_url'         => $home_url,
			'cta_label'        => isset( $content['cta_label'] ) && is_string( $content['cta_label'] ) ? $content['cta_label'] : '',
			'cta_url'          => $cta_url,
			'show_recent'      => ! empty( $content['show_recent'] ),
			'recent_posts'     => $recent_posts,
			'use_theme_colors' => ! empty( $content['use_theme_colors'] ),
			'accent'           => $accent,
			'content_mode'     => Sanitizer::sanitize_recovery_content_mode(
				isset( $content['content_mode'] ) ? $content['content_mode'] : 'builtin'
			),
			'page_body_html'   => $page_body_html,
		);
	}

	/**
	 * Build CSS color hints for the recovery wrapper.
	 *
	 * Uses ThemeColors when the content requests theme colors; otherwise uses
	 * the content accent only.
	 *
	 * @param array $content Normalized content.
	 * @return array{accent:string,background:string,text:string}
	 */
	private static function colors_for_content( array $content ) {
		if ( ! empty( $content['use_theme_colors'] ) ) {
			$colors = ThemeColors::resolve();
			if ( '' === $colors['accent'] ) {
				$colors['accent'] = $content['accent'];
			}

			return $colors;
		}

		return array(
			'accent'     => $content['accent'],
			'background' => '',
			'text'       => '',
		);
	}
}
