<?php
/**
 * PostGate Term Meta Class
 *
 * Per-term paywall/access configuration for taxonomies that belong to
 * the post types enabled in plugin settings. Uses a Select2-ready
 * multi-select for Stripe product IDs (titles shown, IDs saved).
 *
 * @package PostGate
 * @subpackage Admin
 * @since 1.0.0
 * @author QodeSphere
 * @license GPL-2.0+
 */

defined( 'ABSPATH' ) || exit;

/**
 * PostGate Term Meta Class
 *
 * Manages term-level PostGate settings and meta fields for taxonomy terms.
 * Provides configuration options for lock status, lock mode, preview settings,
 * and custom Stripe pricing on a per-term basis for taxonomies associated
 * with enabled post types.
 *
 * This class handles term meta field registration, form rendering, data validation,
 * and saving of term-specific PostGate configuration that can override global
 * settings and be inherited by individual posts.
 *
 * @since 1.0.0
 */
class PostGate_Term_Meta {

	/**
	 * Valid lock override values.
	 *
	 * @since 1.0.0
	 * @var array
	 */
	const LOCK_OVERRIDE_OPTIONS = [ 'inherit', 'lock', 'unlock' ];

	/**
	 * Valid lock mode values.
	 *
	 * @since 1.0.0
	 * @var array
	 */
	const LOCK_MODE_OPTIONS = [ 'inherit', 'paywall', 'login' ];

	/**
	 * Initialize the class and hook into WordPress.
	 *
	 * @since 1.0.0
	 * @return void
	 */
	public static function init(): void {
		add_action( 'init', [ __CLASS__, 'register_term_meta' ] );
		add_action( 'admin_init', [ __CLASS__, 'hook_taxonomy_forms' ] );
	}

	/**
	 * Get all public taxonomies that should have PostGate meta fields.
	 *
	 * Returns all taxonomies with show_ui=true, allowing term-level PostGate
	 * configuration regardless of whether the associated post types are enabled
	 * in PostGate settings. This allows selective locking of individual posts
	 * via term inheritance.
	 *
	 * @since 1.0.0
	 * @return array Array of taxonomy slugs.
	 */
	private static function enabled_taxonomies(): array {
		$taxonomies = get_taxonomies( [ 'show_ui' => true ], 'objects' );
		$taxonomy_slugs = [];

		if ( empty( $taxonomies ) || ! is_array( $taxonomies ) ) {
			return $taxonomy_slugs;
		}

		foreach ( $taxonomies as $taxonomy_slug => $taxonomy_object ) {
			$taxonomy_slugs[] = $taxonomy_slug;
		}

		return $taxonomy_slugs;
	}

	/**
	 * Register term meta for all public taxonomies.
	 *
	 * Meta fields are registered for all taxonomies with show_ui=true,
	 * allowing term-level configuration regardless of enabled post types setting.
	 *
	 * @since 1.0.0
	 * @return void
	 */
	public static function register_term_meta(): void {
		$enabled_taxonomies = self::enabled_taxonomies();
		if ( empty( $enabled_taxonomies ) ) {
			return;
		}

		foreach ( $enabled_taxonomies as $taxonomy_slug ) {
			$taxonomy_object = get_taxonomy( $taxonomy_slug );
			if ( ! $taxonomy_object ) {
				continue;
			}
			$required_capability = isset( $taxonomy_object->cap->manage_terms ) ? $taxonomy_object->cap->manage_terms : 'manage_categories';
			$auth_callback = function () use ($required_capability) {
				return current_user_can( $required_capability );
			};

			register_term_meta(
				$taxonomy_slug,
				'_postgate_lock_override',
				[
					'type' => 'string',
					'default' => 'inherit',
					'sanitize_callback' => [ __CLASS__, 'sanitize_lock_override' ],
					'single' => true,
					'show_in_rest' => true,
					'auth_callback' => $auth_callback,
				]
			);

			register_term_meta(
				$taxonomy_slug,
				'_postgate_lock_mode',
				[
					'type' => 'string',
					'default' => 'inherit',
					'sanitize_callback' => [ __CLASS__, 'sanitize_lock_mode' ],
					'single' => true,
					'show_in_rest' => true,
					'auth_callback' => $auth_callback,
				]
			);

			// Product-based meta (array of Stripe product IDs).
			register_term_meta(
				$taxonomy_slug,
				'_postgate_product_ids',
				[
					'type' => 'array',
					'single' => true,
					'default' => [],
					'show_in_rest' => [
						'schema' => [
							'type' => 'array',
							'items' => [ 'type' => 'string' ],
						],
					],
					'sanitize_callback' => [ __CLASS__, 'sanitize_product_ids' ],
					'auth_callback' => $auth_callback,
				]
			);
		}
	}

	/**
	 * Hook taxonomy add/edit forms and save actions for all public taxonomies.
	 *
	 * @since 1.0.0
	 * @return void
	 */
	public static function hook_taxonomy_forms(): void {
		$enabled_taxonomies = self::enabled_taxonomies();
		if ( empty( $enabled_taxonomies ) ) {
			return;
		}

		foreach ( $enabled_taxonomies as $taxonomy_slug ) {
			add_action( "{$taxonomy_slug}_add_form_fields", [ __CLASS__, 'render_add_fields' ] );
			add_action( "{$taxonomy_slug}_edit_form_fields", [ __CLASS__, 'render_edit_fields' ], 10, 2 );
			add_action( "created_{$taxonomy_slug}", [ __CLASS__, 'save_term_meta' ], 10, 2 );
			add_action( "edited_{$taxonomy_slug}", [ __CLASS__, 'save_term_meta' ], 10, 2 );
		}
	}

	/**
	 * Sanitize lock override value.
	 *
	 * @since 1.0.0
	 * @param mixed $value The value to sanitize.
	 * @return string Sanitized lock override value.
	 */
	public static function sanitize_lock_override( $value ): string {
		$value = is_string( $value ) ? $value : '';
		return in_array( $value, self::LOCK_OVERRIDE_OPTIONS, true ) ? $value : 'inherit';
	}

	/**
	 * Sanitize lock mode value.
	 *
	 * @since 1.0.0
	 * @param mixed $value The value to sanitize.
	 * @return string Sanitized lock mode value.
	 */
	public static function sanitize_lock_mode( $value ): string {
		$value = is_string( $value ) ? $value : '';
		return in_array( $value, self::LOCK_MODE_OPTIONS, true ) ? $value : 'inherit';
	}

	/**
	 * Sanitize an array of Stripe product IDs.
	 *
	 * @since 1.0.0
	 * @param mixed $value The value to sanitize.
	 * @return array Sanitized array of product IDs.
	 */
	public static function sanitize_product_ids( $value ): array {
		if ( ! is_array( $value ) ) {
			return [];
		}

		$sanitized_product_ids = [];
		foreach ( $value as $product_item ) {
			$product_item = sanitize_text_field( (string) $product_item );
			if ( '' !== $product_item ) {
				$sanitized_product_ids[] = $product_item;
			}
		}

		return array_values( array_unique( $sanitized_product_ids ) );
	}

	/**
	 * Build a map of Stripe products (current mode) from options.
	 *
	 * @since 1.0.0
	 * @return array Array of product ID => name pairs.
	 */
	private static function products_map(): array {
		$stripe_mode = get_option( 'postgate_stripe_connect_mode', 'sandbox' ) === 'live' ? 'live' : 'sandbox';
		$all_products = get_option( 'postgate_stripe_products' );
		$products_map = [];

		if ( is_array( $all_products ) && ! empty( $all_products[ $stripe_mode ]['products'] ) && is_array( $all_products[ $stripe_mode ]['products'] ) ) {
			foreach ( $all_products[ $stripe_mode ]['products'] as $product_item ) {
				if ( empty( $product_item['id'] ) ) {
					continue;
				}
				$product_id = (string) $product_item['id'];
				$product_name = isset( $product_item['name'] ) && '' !== $product_item['name'] ? (string) $product_item['name'] : $product_id;
				$products_map[ $product_id ] = $product_name;
			}
			asort( $products_map, SORT_NATURAL | SORT_FLAG_CASE );
		}

		return $products_map;
	}

	/**
	 * Render add form fields for new terms.
	 *
	 * @since 1.0.0
	 * @return void
	 */
	public static function render_add_fields(): void {
		wp_nonce_field( 'postgate_save_term_meta', 'postgate_term_meta_nonce' );

		$default_lock_override = 'inherit';
		$default_lock_mode = 'inherit';
		$available_products = self::products_map();

		$escape_selected = function ( $current_value, $option_value ) {
			return selected( (string) $current_value, (string) $option_value, false );
		};
		?>
		<h2 class="title" style="margin-top:.5em;"><?php esc_html_e( 'PostGate: Content Protection', 'postgate' ); ?></h2>

		<div class="form-field">
			<label for="postgate_lock_override"><?php esc_html_e( 'Lock Status', 'postgate' ); ?></label>
			<select id="postgate_lock_override" name="postgate_lock_override" class="postform">
				<option value="inherit" <?php echo wp_kses_post( $escape_selected( $default_lock_override, 'inherit' ) ); ?>>
					<?php esc_html_e( 'Inherit (settings/parents)', 'postgate' ); ?>
				</option>
				<option value="lock" <?php echo wp_kses_post( $escape_selected( $default_lock_override, 'lock' ) ); ?>>
					<?php esc_html_e( 'Lock', 'postgate' ); ?>
				</option>
				<option value="unlock" <?php echo wp_kses_post( $escape_selected( $default_lock_override, 'unlock' ) ); ?>>
					<?php esc_html_e( 'Unlock', 'postgate' ); ?>
				</option>
			</select>
			<p class="description">
				<?php esc_html_e( 'Control lock status for all posts in this category/term. "Inherit" follows site-wide settings. "Lock" requires payment/login for all posts in this term. "Unlock" makes all posts in this term publicly accessible.', 'postgate' ); ?>
			</p>
		</div>

		<div class="form-field">
			<label for="postgate_lock_mode"><?php esc_html_e( 'Lock Mode', 'postgate' ); ?></label>
			<select id="postgate_lock_mode" name="postgate_lock_mode" class="postform">
				<option value="inherit" <?php echo wp_kses_post( $escape_selected( $default_lock_mode, 'inherit' ) ); ?>>
					<?php esc_html_e( 'Inherit', 'postgate' ); ?>
				</option>
				<option value="paywall" <?php echo wp_kses_post( $escape_selected( $default_lock_mode, 'paywall' ) ); ?>>
					<?php esc_html_e( 'Paywall', 'postgate' ); ?>
				</option>
				<option value="login" <?php echo wp_kses_post( $escape_selected( $default_lock_mode, 'login' ) ); ?>>
					<?php esc_html_e( 'Login', 'postgate' ); ?>
				</option>
			</select>
			<p class="description">
				<?php esc_html_e( 'Choose how users access locked posts in this category/term. "Paywall" requires payment/purchase. "Login" only requires an existing account. "Inherit" uses the site-wide default mode.', 'postgate' ); ?>
			</p>
		</div>

		<div class="form-field">
			<label for="postgate_product_ids"><?php esc_html_e( 'Product Assignment', 'postgate' ); ?></label>

			<input type="hidden" name="postgate_product_ids__present" value="1" />

			<div class="postgate-select2-wrap">
				<select class="postgate-product-ids" id="postgate_product_ids" name="postgate_product_ids[]" multiple
					data-placeholder="<?php echo esc_attr__( 'Search for products...', 'postgate' ); ?>" style="width:100%;">
					<?php foreach ( $available_products as $product_id => $product_name ) : ?>
						<option value="<?php echo esc_attr( $product_id ); ?>"><?php echo esc_html( $product_name ); ?></option>
					<?php endforeach; ?>
				</select>
			</div>

			<p class="description">
				<?php esc_html_e( 'Assign specific products for all posts in this category/term. Leave empty to use the site-wide default product. Users who purchase any of the selected products will unlock all posts in this term.', 'postgate' ); ?>
			</p>
		</div>
		<?php
	}

	/**
	 * Render edit form fields for existing terms.
	 *
	 * @since 1.0.0
	 * @param WP_Term $term The term object.
	 * @return void
	 */
	public static function render_edit_fields( $term ): void {
		wp_nonce_field( 'postgate_save_term_meta', 'postgate_term_meta_nonce' );

		$current_lock_override = get_term_meta( $term->term_id, '_postgate_lock_override', true ) ?: 'inherit';
		$current_lock_mode = get_term_meta( $term->term_id, '_postgate_lock_mode', true ) ?: 'inherit';
		$current_product_ids = get_term_meta( $term->term_id, '_postgate_product_ids', true );
		$current_product_ids = is_array( $current_product_ids ) ? array_values( array_filter( array_map( 'strval', $current_product_ids ) ) ) : [];

		$available_products = self::products_map();
		$escape_selected = function ( $current_value, $option_value ) {
			return selected( (string) $current_value, (string) $option_value, false );
		};
		?>
		<tr class="form-field">
			<th scope="row" colspan="2">
				<h2 class="title" style="margin:0;"><?php esc_html_e( 'PostGate: Content Protection', 'postgate' ); ?></h2>
			</th>
		</tr>

		<tr class="form-field">
			<th scope="row"><label for="postgate_lock_override"><?php esc_html_e( 'Lock Status', 'postgate' ); ?></label></th>
			<td>
				<select id="postgate_lock_override" name="postgate_lock_override">
					<option value="inherit" <?php echo wp_kses_post( $escape_selected( $current_lock_override, 'inherit' ) ); ?>>
						<?php esc_html_e( 'Inherit (settings/parents)', 'postgate' ); ?>
					</option>
					<option value="lock" <?php echo wp_kses_post( $escape_selected( $current_lock_override, 'lock' ) ); ?>>
						<?php esc_html_e( 'Lock', 'postgate' ); ?>
					</option>
					<option value="unlock" <?php echo wp_kses_post( $escape_selected( $current_lock_override, 'unlock' ) ); ?>>
						<?php esc_html_e( 'Unlock', 'postgate' ); ?>
					</option>
				</select>
				<p class="description">
					<?php esc_html_e( 'Control lock status for all posts in this category/term. "Inherit" follows site-wide settings. "Lock" requires payment/login for all posts in this term. "Unlock" makes all posts in this term publicly accessible.', 'postgate' ); ?>
				</p>
			</td>
		</tr>

		<tr class="form-field">
			<th scope="row"><label for="postgate_lock_mode"><?php esc_html_e( 'Lock Mode', 'postgate' ); ?></label></th>
			<td>
				<select id="postgate_lock_mode" name="postgate_lock_mode">
					<option value="inherit" <?php echo wp_kses_post( $escape_selected( $current_lock_mode, 'inherit' ) ); ?>>
						<?php esc_html_e( 'Inherit', 'postgate' ); ?>
					</option>
					<option value="paywall" <?php echo wp_kses_post( $escape_selected( $current_lock_mode, 'paywall' ) ); ?>>
						<?php esc_html_e( 'Paywall', 'postgate' ); ?>
					</option>
					<option value="login" <?php echo wp_kses_post( $escape_selected( $current_lock_mode, 'login' ) ); ?>>
						<?php esc_html_e( 'Login', 'postgate' ); ?>
					</option>
				</select>
				<p class="description">
					<?php esc_html_e( 'Choose how users access locked posts in this category/term. "Paywall" requires payment/purchase. "Login" only requires an existing account. "Inherit" uses the site-wide default mode.', 'postgate' ); ?>
				</p>
			</td>
		</tr>

		<tr class="form-field">
			<th scope="row"><label for="postgate_product_ids"><?php esc_html_e( 'Product Assignment', 'postgate' ); ?></label>
			</th>
			<td>
				<input type="hidden" name="postgate_product_ids__present" value="1" />

				<div class="postgate-select2-wrap">
					<select class="postgate-product-ids" id="postgate_product_ids" name="postgate_product_ids[]" multiple
						data-placeholder="<?php echo esc_attr__( 'Search for products...', 'postgate' ); ?>"
						style="width:100%;">
						<?php foreach ( $available_products as $product_id => $product_name ) : ?>
							<option value="<?php echo esc_attr( $product_id ); ?>" <?php echo selected( in_array( (string) $product_id, $current_product_ids, true ), true, false ); ?>>
								<?php echo esc_html( $product_name ); ?>
							</option>
						<?php endforeach; ?>
					</select>
				</div>

				<p class="description">
					<?php esc_html_e( 'Assign specific products for all posts in this category/term. Leave empty to use the site-wide default product. Users who purchase any of the selected products will unlock all posts in this term.', 'postgate' ); ?>
				</p>
			</td>
		</tr>
		<?php
	}

	/**
	 * Save term meta data.
	 *
	 * Handles nonce verification, capability checks, and saves all PostGate
	 * term meta fields with proper sanitization.
	 *
	 * @since 1.0.0
	 * @param int $term_id The term ID being saved.
	 * @return void
	 */
	public static function save_term_meta( int $term_id ): void {
		// Verify nonce.
		if ( ! isset( $_POST['postgate_term_meta_nonce'] ) ) {
			return;
		}
		$nonce = sanitize_text_field( wp_unslash( $_POST['postgate_term_meta_nonce'] ) );
		if ( ! wp_verify_nonce( $nonce, 'postgate_save_term_meta' ) ) {
			// phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound
			do_action( 'wp_verify_nonce_failed', $nonce, 'postgate_save_term_meta', wp_get_current_user(), '' );
			return;
		}

		// Get term and check capabilities.
		$term_object = get_term( (int) $term_id );
		if ( ! $term_object || is_wp_error( $term_object ) ) {
			return;
		}
		$taxonomy_object = get_taxonomy( $term_object->taxonomy );
		$required_capability = is_object( $taxonomy_object ) && isset( $taxonomy_object->cap->manage_terms ) ? $taxonomy_object->cap->manage_terms : 'manage_categories';
		if ( ! current_user_can( $required_capability ) ) {
			return;
		}

		// Lock override (sanitize before use).
		$lock_override_raw = (string) filter_input( INPUT_POST, 'postgate_lock_override', FILTER_UNSAFE_RAW );
		$lock_override_raw = '' !== $lock_override_raw ? sanitize_text_field( $lock_override_raw ) : '';
		$lock_override = ( '' !== $lock_override_raw ) ? self::sanitize_lock_override( $lock_override_raw ) : '';

		// Lock mode (sanitize before use).
		$lock_mode_raw = (string) filter_input( INPUT_POST, 'postgate_lock_mode', FILTER_UNSAFE_RAW );
		$lock_mode_raw = '' !== $lock_mode_raw ? sanitize_text_field( $lock_mode_raw ) : '';
		$lock_mode = ( '' !== $lock_mode_raw ) ? self::sanitize_lock_mode( $lock_mode_raw ) : '';

		// Presence marker (allows clearing to inherit).
		$product_ids_present_raw = (string) filter_input( INPUT_POST, 'postgate_product_ids__present', FILTER_UNSAFE_RAW );
		$product_ids_present = ( '1' === sanitize_text_field( $product_ids_present_raw ) );

		// Get product_ids array from $_POST.
		// phpcs:ignore WordPress.Security.NonceVerification.Missing -- Nonce verified at start of save_term_meta method.
		$raw_product_ids = [];
		if ( isset( $_POST['postgate_product_ids'] ) && is_array( $_POST['postgate_product_ids'] ) ) {
			// phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Sanitized below with array_map.
			$raw_product_ids = array_map( 'sanitize_text_field', array_map( 'strval', wp_unslash( $_POST['postgate_product_ids'] ) ) );
		}

		// Save lock override field (only if submitted).
		if ( '' !== $lock_override_raw ) {
			update_term_meta( $term_id, '_postgate_lock_override', $lock_override );
		}

		// Save lock mode field (only if submitted).
		if ( '' !== $lock_mode_raw ) {
			update_term_meta( $term_id, '_postgate_lock_mode', $lock_mode );
		}

		// Save product IDs field (array) — product-based architecture.
		if ( $product_ids_present ) {
			$product_ids = self::sanitize_product_ids( $raw_product_ids );

			if ( ! empty( $product_ids ) ) {
				update_term_meta( $term_id, '_postgate_product_ids', $product_ids );
			} else {
				// Explicitly empty => remove to inherit.
				delete_term_meta( $term_id, '_postgate_product_ids' );
			}
		}
	}
}
