<?php
/**
 * PostGate Template Renderer Class
 *
 * Centralizes all template loading and rendering logic for PostGate gates.
 * Handles paywall and login templates with theme override support, providing
 * a consistent rendering interface across the plugin while allowing theme
 * developers to customize gate appearance.
 *
 * @package PostGate
 * @subpackage Core
 * @since 1.0.0
 * @author QodeSphere
 * @license GPL-2.0+
 */

defined( 'ABSPATH' ) || exit;

/**
 * PostGate Template Renderer Class
 *
 * Provides centralized template rendering for all PostGate lock gates.
 * This class implements a consistent rendering interface with the following features:
 *
 * - Theme override support via WordPress locate_template()
 * - Multiple template variants (compact, classic layouts)
 * - Custom context injection for template customization
 * - Automatic payment option data formatting from products
 * - Default product fallback when no products configured
 * - Minimal HTML fallbacks when templates are missing
 * - Filter hooks for URL customization
 * - Safe variable extraction with EXTR_SKIP
 *
 * Template Loading Priority:
 * 1. Theme template: /postgate/{template-name}.php
 * 2. Plugin template: POSTGATE_PATH/templates/{template-name}.php
 * 3. Fallback HTML: Minimal hardcoded markup
 *
 * Templates receive pre-prepared variables:
 * - $postgate_post_id: The locked post ID
 * - $postgate_price_ids: Array of price IDs from products
 * - $postgate_price_options: Formatted payment option data for display
 * - $postgate_default_price_id: Pre-selected default price from product
 * - $postgate_login_url: Login URL with redirect
 * - Additional context variables via extract()
 *
 * @since 1.0.0
 */
class PostGate_Template_Renderer {

	/**
	 * Render a paywall gate using the specified template.
	 *
	 * Loads and renders the paywall template with complete payment option preparation:
	 * 1. Sanitizes and validates input parameters
	 * 2. Injects default product price if price_ids array is empty
	 * 3. Fetches and formats price data from product configuration
	 * 4. Determines default/pre-selected price
	 * 5. Locates template file (theme override or plugin fallback)
	 * 6. Prepares template variables and renders output
	 * 7. Falls back to minimal HTML if template not found
	 *
	 * Template Variables Provided:
	 * - $postgate_post_id: Post ID being locked
	 * - $postgate_price_ids: Array of Stripe price IDs from products
	 * - $postgate_price_options: Formatted array of payment option objects with:
	 *   * id: Stripe price ID
	 *   * title: Product name
	 *   * description: Product description
	 *   * amount: Price amount in cents
	 *   * currency: Currency code (e.g., 'USD')
	 *   * interval: Billing interval for subscriptions ('month', 'year', etc.)
	 *   * type: 'one_time' or 'recurring'
	 * - $postgate_default_price_id: Pre-selected price ID from default product
	 * - $postgate_login_url: Login URL (filtered via 'postgate_login_url')
	 * - Custom context variables via extract()
	 *
	 * @since 1.0.0
	 *
	 * @param int    $post_id  The post ID being locked.
	 * @param array  $price_ids Array of Stripe price IDs from products to display.
	 * @param string $template Template name: 'compact' or 'classic' (default: 'compact').
	 * @param array  $context  Optional. Additional context variables for template customization.
	 *                         Common keys: 'custom_title', 'button_text', etc.
	 *
	 * @return string Rendered HTML for the paywall gate.
	 */
	public static function render_paywall_gate( int $post_id, array $price_ids, string $template = 'compact', array $context = [] ): string {
		$template = sanitize_key( $template ) ?: 'compact';
		$post_id = (int) $post_id;
		$price_ids = array_values( array_unique( array_map( 'strval', $price_ids ) ) );

		// Safety net: inject default product price when empty
		if ( empty( $price_ids ) ) {
			$default_product_id = PostGate_Product_Utils::get_default_product_id();
			if ( $default_product_id !== '' ) {
				$default_price = PostGate_Product_Utils::get_default_price_for_product( $default_product_id );
				if ( $default_price !== '' ) {
					$price_ids = [ $default_price ];
				}
			}
		}

		// Get and format payment options from products
		$collected = PostGate_Product_Utils::get_prices_for_ids( $price_ids );

		$postgate_default_price_id = $collected['default_id'] ?: ( $collected['items'][0]['id'] ?? '' );
		$postgate_price_options = PostGate_Product_Utils::format_price_options_grouped( $collected['items'] );

		// Locate template file (theme override support)
		$template_name = "postgate/paywall-{$template}.php";
		$template_file = self::locate_template( $template_name );

		// Render template
		ob_start();
		if ( $template_file && file_exists( $template_file ) ) {
			// Prepare variables expected by template
			$postgate_post_id = $post_id;
			$postgate_price_ids = $price_ids;
			$postgate_login_url = apply_filters( 'postgate_login_url', wp_login_url( get_permalink( $post_id ) ), $post_id, $context );
			// Extract context variables for template
			if ( ! empty( $context ) ) {
				extract( $context, EXTR_SKIP );
			}
			include $template_file;
		} else {
			// Minimal fallback
			self::render_fallback_paywall( $postgate_price_options, $context );
		}

		return ob_get_clean();
	}

	/**
	 * Get allowed HTML tags and attributes for gate templates.
	 *
	 * Extends wp_kses_post() allowed tags to include form-specific attributes
	 * like 'value', 'checked', and 'data-*' that are required for paywall gates.
	 *
	 * @since 1.0.0
	 * @return array Allowed HTML tags and attributes.
	 */
	public static function get_allowed_gate_html(): array {
		$allowed = wp_kses_allowed_html( 'post' );

		// Add form-specific attributes to input
		if ( ! isset( $allowed['input'] ) ) {
			$allowed['input'] = [];
		}
		$allowed['input'] = array_merge(
			$allowed['input'],
			[
				'type' => true,
				'name' => true,
				'value' => true,
				'checked' => true,
				'class' => true,
				'id' => true,
				'aria-label' => true,
				'data-*' => true,
			]
		);

		// Add form and button attributes
		if ( ! isset( $allowed['form'] ) ) {
			$allowed['form'] = [];
		}
		$allowed['form']['class'] = true;
		$allowed['form']['data-*'] = true;

		if ( ! isset( $allowed['button'] ) ) {
			$allowed['button'] = [];
		}
		$allowed['button']['type'] = true;
		$allowed['button']['class'] = true;
		$allowed['button']['data-*'] = true;

		return $allowed;
	}

	/**
	 * Render a login gate for login-only locked content.
	 *
	 * Loads and renders the login template with the following flow:
	 * 1. Validates post ID parameter
	 * 2. Locates login template (theme override or plugin fallback)
	 * 3. Prepares login URL with redirect back to current post
	 * 4. Applies 'postgate_login_url' filter for customization
	 * 5. Extracts custom context variables
	 * 6. Renders template or falls back to minimal HTML
	 *
	 * Template Variables Provided:
	 * - $postgate_post_id: Post ID being locked
	 * - $postgate_login_url: WordPress login URL with redirect parameter
	 * - Custom context variables via extract()
	 *
	 * The login URL automatically includes a redirect parameter to return
	 * users to the current post after successful authentication.
	 *
	 * @since 1.0.0
	 *
	 * @param int   $post_id The post ID being locked.
	 * @param array $context Optional. Additional context variables for template customization.
	 *                       Common keys: 'custom_title', 'button_text', etc.
	 *
	 * @return string Rendered HTML for the login gate.
	 */
	public static function render_login_gate( int $post_id, array $context = [] ): string {
		$post_id = (int) $post_id;

		// Locate template file
		$template_name = 'postgate/login.php';
		$template_file = self::locate_template( $template_name );

		// Render template
		ob_start();
		if ( $template_file && file_exists( $template_file ) ) {
			// Prepare login URL (available to template as $postgate_login_url)
			$postgate_post_id = $post_id;
			$postgate_login_url = apply_filters( 'postgate_login_url', wp_login_url( get_permalink( $post_id ) ), $post_id, $context );
			// Extract context variables for template (does not override $postgate_login_url)
			if ( ! empty( $context ) ) {
				extract( $context, EXTR_SKIP );
			}
			include $template_file;
		} else {
			// Minimal fallback
			self::render_fallback_login( $post_id, $context );
		}

		return ob_get_clean();
	}

	/**
	 * Locate a template file with theme override support.
	 *
	 * Implements a two-tier template loading system:
	 * 1. Theme Override: Checks active theme directory for custom template
	 * 2. Plugin Fallback: Uses plugin's built-in template if no override exists
	 *
	 * Theme Override Path:
	 * - wp-content/themes/{active-theme}/postgate/{template-name}.php
	 *
	 * Plugin Fallback Path:
	 * - wp-content/plugins/postgate/templates/{template-name}.php
	 *
	 * This allows theme developers to customize gate appearance without
	 * modifying plugin files, following WordPress template hierarchy best practices.
	 *
	 * @since 1.0.0
	 *
	 * @param string $template_name Template file name relative to 'postgate/' directory.
	 *                              Example: 'postgate/paywall-compact.php'
	 *
	 * @return string|false Full path to template file, or false if not found.
	 */
	private static function locate_template( string $template_name ) {
		// Check theme override first
		$theme_template = locate_template( [ $template_name ] );
		if ( $theme_template ) {
			return $theme_template;
		}

		// Fall back to plugin template
		$plugin_template = trailingslashit( POSTGATE_PATH ) . 'templates/' . basename( $template_name );
		if ( file_exists( $plugin_template ) ) {
			return $plugin_template;
		}

		return false;
	}

	/**
	 * Render minimal fallback paywall HTML when template is not found.
	 *
	 * Provides a basic paywall interface when neither theme override nor
	 * plugin template is available. This ensures the plugin continues to
	 * function even if template files are missing or corrupted.
	 *
	 * The fallback displays:
	 * - Custom or default title from context
	 * - Error message if no prices are configured
	 * - Generic purchase message if prices exist
	 *
	 * This method should only be called as a last resort when template
	 * loading fails. Normal operation should always use proper templates.
	 *
	 * @since 1.0.0
	 *
	 * @param array $price_options Formatted price options array (may be empty).
	 * @param array $context       Optional. Additional context for customization.
	 *                             Supports 'custom_title' key.
	 *
	 * @return void Outputs HTML directly.
	 */
	private static function render_fallback_paywall( array $price_options, array $context = [] ): void {
		$title = $context['custom_title'] ?? __( 'This content is locked', 'postgate' );
		?>
		<div class="postgate-locked-article postgate-lock--paywall">
			<h3 class="postgate-title"><?php echo esc_html( $title ); ?></h3>
			<?php if ( empty( $price_options ) ) : ?>
				<p class="postgate-text">
					<?php esc_html_e( 'No payment options configured. Configure Stripe products in PostGate settings.', 'postgate' ); ?>
				</p>
			<?php else : ?>
				<p class="postgate-text">
					<?php esc_html_e( 'Purchase access to unlock this content.', 'postgate' ); ?>
				</p>
			<?php endif; ?>
		</div>
		<?php
	}

	/**
	 * Render minimal fallback login HTML when template is not found.
	 *
	 * Provides a basic login gate interface when neither theme override nor
	 * plugin template is available. This ensures the plugin continues to
	 * function even if template files are missing or corrupted.
	 *
	 * The fallback displays:
	 * - Custom or default title from context
	 * - Custom or default button text from context
	 * - Login link with redirect to current post
	 *
	 * This method should only be called as a last resort when template
	 * loading fails. Normal operation should always use proper templates.
	 *
	 * @since 1.0.0
	 *
	 * @param int   $post_id The post ID for generating login redirect URL.
	 * @param array $context Optional. Additional context for customization.
	 *                       Supports 'custom_title' and 'button_text' keys.
	 *
	 * @return void Outputs HTML directly.
	 */
	private static function render_fallback_login( int $post_id, array $context = [] ): void {
		$title = $context['custom_title'] ?? __( 'This content requires login', 'postgate' );
		$button_text = $context['button_text'] ?? __( 'Log in to continue', 'postgate' );
		$login_url = wp_login_url( get_permalink( $post_id ) );
		?>
		<div class="postgate-locked-article postgate-lock--login">
			<h3 class="postgate-title"><?php echo esc_html( $title ); ?></h3>
			<p class="postgate-text">
				<a class="postgate-button primary" href="<?php echo esc_url( $login_url ); ?>">
					<?php echo esc_html( $button_text ); ?>
				</a>
			</p>
		</div>
		<?php
	}

	/**
	 * Render archive lock indicator for locked posts on archive pages.
	 *
	 * Displays a visual indicator on archive pages (homepage, categories, tags, search)
	 * showing that a post is protected. This is an alternative to showing excerpts,
	 * providing more visual emphasis on the locked status.
	 *
	 * Loads template with the following flow:
	 * 1. Validates post ID parameter
	 * 2. Locates archive-lock template (theme override or plugin fallback)
	 * 3. Prepares post URL for linking to full content
	 * 4. Extracts custom context variables
	 * 5. Renders template or falls back to minimal HTML
	 *
	 * Template Variables Provided:
	 * - $postgate_post_id: Post ID being displayed
	 * - $postgate_post_url: URL to the full post
	 * - Custom context variables via extract()
	 *
	 * @since 1.0.0
	 *
	 * @param int   $post_id The post ID to display the lock indicator for.
	 * @param array $context Optional. Additional context variables for template customization.
	 *
	 * @return string Rendered HTML for the archive lock indicator.
	 */
	public static function render_archive_lock( int $post_id, array $context = [] ): string {
		$post_id = (int) $post_id;

		// Locate template file
		$template_name = 'postgate/archive-lock.php';
		$template_file = self::locate_template( $template_name );

		// Render template
		ob_start();
		if ( $template_file && file_exists( $template_file ) ) {
			// Prepare variables for template
			$postgate_post_id = $post_id;
			$postgate_post_url = get_permalink( $post_id );
			// Extract context variables for template
			if ( ! empty( $context ) ) {
				extract( $context, EXTR_SKIP );
			}
			include $template_file;
		} else {
			// Minimal fallback
			self::render_fallback_archive_lock( $post_id, $context );
		}

		return ob_get_clean();
	}

	/**
	 * Render minimal fallback archive lock HTML when template is not found.
	 *
	 * Provides a basic lock indicator when neither theme override nor
	 * plugin template is available. This ensures the plugin continues to
	 * function even if template files are missing or corrupted.
	 *
	 * @since 1.0.0
	 *
	 * @param int   $post_id The post ID for generating permalink.
	 * @param array $context Optional. Additional context for customization.
	 *
	 * @return void Outputs HTML directly.
	 */
	private static function render_fallback_archive_lock( int $post_id, array $context = [] ): void {
		$title = __( 'This content is protected', 'postgate' );
		$button_text = __( 'View post to unlock', 'postgate' );
		$post_url = get_permalink( $post_id );
		?>
		<div class="postgate-archive-lock">
			<h3><?php echo esc_html( $title ); ?></h3>
			<p>
				<a href="<?php echo esc_url( $post_url ); ?>">
					<?php echo esc_html( $button_text ); ?>
				</a>
			</p>
		</div>
		<?php
	}

}
