<?php
/**
 * PostGate Lock Block
 *
 * Implements the Gutenberg block for content-level access control within posts.
 * Allows editors to lock specific sections of content behind login gates or paywalls,
 * providing granular access control at the block level rather than entire posts.
 *
 * @package PostGate
 * @subpackage Blocks
 * @since 1.0.0
 * @author QodeSphere
 * @license GPL-2.0+
 */

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

/**
 * PostGate Lock Block
 *
 * This class registers and manages the PostGate Lock Block, a Gutenberg block that enables
 * content-level monetization and access control. Key features include:
 *
 * - Block registration with dynamic rendering
 * - Post-type filtering to show block only on public post types
 * - Post-level lock override detection to avoid redundant gates
 * - Two lock modes: login-only and paywall
 * - Multiple Stripe price selection for paywall mode
 * - Role-based bypass for administrators and other roles
 * - User access validation via PostGate_Access_Manager
 * - Template selection (compact or classic) for paywall display
 * - Integration with centralized template renderer
 *
 * The block wraps inner content and conditionally displays it based on user access.
 * When access is denied, it renders either a login gate or paywall gate using the
 * PostGate_Template_Renderer for consistent UI across the plugin.
 *
 * Block Attributes:
 * - lockMode: 'login' or 'paywall' (default: 'paywall')
 * - productIds: Array of Stripe product IDs (optional, takes precedence over priceIds)
 * - priceIds: Array of Stripe price IDs (optional fallback when productIds is empty)
 * - paywallTemplate: 'compact' or 'classic' (default: 'compact')
 *
 * @since 1.0.0
 */
class PostGate_Lock_Block {
	/**
	 * Initialize the Lock Block.
	 *
	 * Registers hooks for block registration and filtering allowed block types
	 * based on post type configuration.
	 *
	 * @since 1.0.0
	 *
	 * @return void
	 */
	public static function init(): void {
		add_action( 'init', [ __CLASS__, 'register' ] );
		add_filter( 'allowed_block_types_all', [ __CLASS__, 'filter_allowed_blocks' ], 10, 2 );
	}

	/**
	 * Register the Lock Block with WordPress.
	 *
	 * Registers the block type using block.json from the assets directory and
	 * sets up the dynamic render callback. Exits early if block.json is not found.
	 *
	 * @since 1.0.0
	 *
	 * @return void
	 */
	public static function register(): void {
		$dir = rtrim( POSTGATE_PATH, '/' ) . '/assets/blocks/lock-block';
		if ( ! file_exists( $dir . '/block.json' ) ) {
			return;
		}
		register_block_type( $dir, [ 'render_callback' => [ __CLASS__, 'render' ] ] );
	}

	/**
	 * Filter allowed block types to only show PostGate Lock Block on public post types.
	 *
	 * Ensures the lock block only appears for public post types. Private or internal
	 * post types (attachments, nav_menu_item, revisions, etc.) should not have lock blocks.
	 * This provides consistency with meta box and sidebar availability, allowing granular
	 * content locking on all public post types regardless of global settings.
	 *
	 * @since 1.0.0
	 * @param bool|string[] $allowed_block_types Array of allowed block types, or true/false.
	 * @param object        $block_editor_context The current block editor context.
	 * @return bool|string[] Modified array of allowed block types.
	 */
	public static function filter_allowed_blocks( $allowed_block_types, $block_editor_context ) {
		// If allowed_block_types is true (all blocks allowed), we need to get all registered blocks
		if ( true === $allowed_block_types ) {
			$allowed_block_types = array_keys( \WP_Block_Type_Registry::get_instance()->get_all_registered() );
		}

		// If it's false or not an array, return as is
		if ( false === $allowed_block_types || ! is_array( $allowed_block_types ) ) {
			return $allowed_block_types;
		}

		// Check if we have a post context
		if ( ! isset( $block_editor_context->post ) || ! $block_editor_context->post instanceof \WP_Post ) {
			return $allowed_block_types;
		}

		$post_type = $block_editor_context->post->post_type;

		// Check if this is a public post type
		// Lock blocks should be available for all public post types (posts, pages, CPTs)
		// to allow granular content locking regardless of global settings
		$post_type_object = get_post_type_object( $post_type );
		if ( ! $post_type_object || ! $post_type_object->public ) {
			// Not a public post type (attachment, nav_menu_item, etc.) - remove lock block
			$lock_block_key = array_search( 'postgate/lock-block', $allowed_block_types, true );
			if ( false !== $lock_block_key ) {
				unset( $allowed_block_types[ $lock_block_key ] );
				$allowed_block_types = array_values( $allowed_block_types );
			}
		}

		return $allowed_block_types;
	}



	/**
	 * Render the Lock Block content based on user access.
	 *
	 * This is the dynamic render callback for the block. It determines whether to display
	 * the inner content or a gate based on:
	 * 1. Post-level lock override (suppresses block-level gates if post is locked)
	 * 2. Role-based bypass for privileged users
	 * 3. Lock mode (login or paywall)
	 * 4. User access via PostGate_Access_Manager
	 *
	 * When access is granted, returns wrapped inner content. When denied, renders
	 * appropriate gate template (login or paywall) using PostGate_Template_Renderer.
	 *
	 * @since 1.0.0
	 *
	 * @param array  $attributes Block attributes including lockMode, productIds, priceIds, and paywallTemplate.
	 * @param string $content    Inner block content (wrapped blocks).
	 *
	 * @return string The rendered HTML output for the block.
	 */
	public static function render( array $attributes, string $content ): string {
		$post_id = get_the_ID();
		if ( ! $post_id ) {
			return $content;
		}

		// If the post is explicitly locked at the post-level, suppress block-level gates
		// to avoid redundant lock messages. The post-level lock gate will handle access control.
		$post_lock_override = get_post_meta( $post_id, '_postgate_lock_override', true );
		if ( $post_lock_override === 'lock' ) {
			// Post is locked at post-level: return inner content without block gate
			// This prevents "This block is locked" from showing when entire post is already gated
			return '<div class="postgate-lock-block">' . $content . '</div>';
		}

		$lock_mode = isset( $attributes['lockMode'] ) ? (string) $attributes['lockMode'] : 'paywall';
		$price_ids = isset( $attributes['priceIds'] ) && is_array( $attributes['priceIds'] ) ? $attributes['priceIds'] : [];
		$product_ids = isset( $attributes['productIds'] ) && is_array( $attributes['productIds'] ) ? $attributes['productIds'] : [];

		// Use product IDs to extract all available price options
		if ( $lock_mode === 'paywall' && ! empty( $product_ids ) ) {
			// Get ALL price IDs from products (not just default) to show all options
			$price_ids = PostGate_Product_Utils::get_all_price_ids_from_products( $product_ids );
		}

		// Fallback to default price if no prices configured
		if ( $lock_mode === 'paywall' && empty( $price_ids ) ) {
			$def = PostGate_Product_Utils::get_default_product_id();
			if ( $def ) {
				$price_ids = [ $def ];
			}
		}

		$bypass_roles = get_option( 'postgate_bypass_roles', [ 'administrator' ] );
		if ( is_user_logged_in() && is_array( $bypass_roles ) ) {
			$current = wp_get_current_user();
			if ( array_intersect( (array) $current->roles, $bypass_roles ) ) {
				return $content;
			}
		}

		$user_id = get_current_user_id();
		$has_access = false;

		if ( $lock_mode === 'login' ) {
			$has_access = is_user_logged_in();
		} else { // paywall
			if ( $user_id ) {
				if ( PostGate_Access_Manager::has_post_access( $user_id, $post_id ) ) {
					$has_access = true;
				} elseif ( ! empty( $price_ids ) ) {
					$has_access = PostGate_Access_Manager::has_block_access( $user_id, $post_id, $price_ids );
				}
			}
		}

		if ( $has_access ) {
			return '<div class="postgate-lock-block">' . $content . '</div>';
		}

		// Get template choice from block attributes (default: compact)
		$template = isset( $attributes['paywallTemplate'] ) ? (string) $attributes['paywallTemplate'] : 'compact';
		$template = in_array( $template, [ 'compact', 'classic' ], true ) ? $template : 'compact';

		ob_start();
		echo '<div class="postgate-lock-block">';

		if ( $lock_mode === 'login' ) {
			// Render login gate using centralized template renderer
			// wp_kses allows safe HTML tags while stripping malicious code
			echo wp_kses( PostGate_Template_Renderer::render_login_gate( $post_id ), PostGate_Template_Renderer::get_allowed_gate_html() );
		} else {
			// Render paywall template (compact or classic) with custom context for blocks
			$context = [ 'custom_title' => __( 'This block is locked', 'postgate' ) ];
			// wp_kses allows safe HTML tags while stripping malicious code
			echo wp_kses( PostGate_Template_Renderer::render_paywall_gate( $post_id, $price_ids, $template, $context ), PostGate_Template_Renderer::get_allowed_gate_html() );
		}

		echo '</div>';
		return ob_get_clean();
	}
}
