<?php
/**
 * File Path: includes/metaboxes/class-mbs-metabox-related-products.php
 *
 * Related Products Metabox
 *
 * Renders two sections:
 *   1. Auto-detected: books in this catalog sharing the same Series, Author,
 *      Collection, or Universe taxonomy terms — all license tiers, read-only.
 *   2. Manual ONIX relations: PRO/PUB-gated repeater for ONIX List 51 product
 *      relations (edition succession, format variants).
 *
 * FREE: auto-detected panel only; ONIX editor outputs an HTML comment gate.
 *
 * @package    My_Book_Showroom
 * @subpackage Includes/Metaboxes
 * @since      1.1.0
 */

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

// phpcs:disable WordPress.Security.NonceVerification -- nonce verified in parent class verify_save() / check_admin_referer() before $_POST access; PHPCS cannot trace cross-method nonce verification
// phpcs:disable WordPress.Security.ValidatedSanitizedInput -- input sanitized at point of use (sanitize_text_field, absint, wp_kses_post, etc.); false positives from PHPCS not tracing sanitization helpers called before this method
class MBS_Metabox_Related_Products extends MBS_Metabox_Base { // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedClassFound -- mbs_/MBS_ prefix is correct; scanner not configured with plugin prefix

	public function __construct() {
	}


	// ─────────────────────────────────────────────────────────────────────────
	// Render
	// ─────────────────────────────────────────────────────────────────────────

	public function render( WP_Post $post ): void {
		wp_nonce_field( 'mbs_related_products_save', 'mbs_related_products_nonce' );

		// Section 1 — Auto-detected related books (all license tiers).
		$this->render_auto_related( $post );

		// Section 2 — Manual ONIX relations (PRO/PUB only).
		?>
		<div class="mbs-related-onix-section">
			<h4 class="mbs-related-section-heading">
				<?php esc_html_e( 'ONIX Product Relations', 'my-book-showroom' ); ?>
			</h4>
		<?php
		if ( ! mbs_is_pro() ) {
			MBS_Pro_Gate::comment( 'Related Products ONIX relations editor' );
			echo '</div>';
			return;
		}

		$rows = MBS_Related_Products_DAO::get_for_post( $post->ID );
		?>
			<div id="mbs-related-products-rows">
				<?php foreach ( $rows as $row ) : ?>
					<?php $this->render_row( $row ); ?>
				<?php endforeach; ?>
			</div>
			<button type="button" class="button" id="mbs-add-related-product" style="margin-top:8px;">
				<?php esc_html_e( '+ Add Related Product', 'my-book-showroom' ); ?>
			</button>
			<template id="mbs-related-product-template">
				<?php $this->render_row( null ); ?>
			</template>
		</div>
		<script>
		(function(){
			var addBtn  = document.getElementById('mbs-add-related-product');
			var rowWrap = document.getElementById('mbs-related-products-rows');
			if ( ! addBtn || ! rowWrap ) { return; }
			addBtn.addEventListener('click', function(){
				var tmpl = document.getElementById('mbs-related-product-template').innerHTML;
				var idx  = rowWrap.querySelectorAll('.mbs-related-product-row').length;
				tmpl = tmpl.replace(/__IDX__/g, String(idx));
				rowWrap.insertAdjacentHTML('beforeend', tmpl);
			});
			rowWrap.addEventListener('click', function(e){
				if ( e.target.classList.contains('mbs-remove-related-product') ) {
					e.target.closest('.mbs-related-product-row').remove();
				}
			});
		})();
		</script>
		<?php
	}

	// ─────────────────────────────────────────────────────────────────────────
	// Private: auto-detected panel
	// ─────────────────────────────────────────────────────────────────────────

	/**
	 * Render the auto-detected related books panel.
	 *
	 * Queries books in this catalog that share a taxonomy term with $post.
	 * Groups: Series (sorted by mbs_series_position) → Author → Collection
	 * → Universe. Groups with no results are silently omitted. If no group
	 * has results, a neutral "none found" notice is shown.
	 *
	 * @param WP_Post $post  The book being edited.
	 */
	private function render_auto_related( WP_Post $post ): void {
		$id = $post->ID;

		// Series — sort by mbs_series_position so the reading-order is obvious.
		$series_posts = $this->get_related_by_tax( $id, MBS_TAX_SERIES );
		usort( $series_posts, static function ( WP_Post $a, WP_Post $b ): int {
			$pa = (float) get_post_meta( $a->ID, 'mbs_series_position', true );
			$pb = (float) get_post_meta( $b->ID, 'mbs_series_position', true );
			return $pa <=> $pb;
		} );

		$author_posts     = $this->get_related_by_tax( $id, MBS_TAX_AUTHOR );
		$collection_posts = $this->get_related_by_tax( $id, MBS_TAX_COLLECTION );
		$universe_posts   = $this->get_related_by_tax( $id, MBS_TAX_UNIVERSE );

		$has_any = ! empty( $series_posts ) || ! empty( $author_posts )
				|| ! empty( $collection_posts ) || ! empty( $universe_posts );

		?>
		<style>
		.mbs-related-auto-panel { margin-bottom: 4px; }
		.mbs-related-auto-row {
			display: flex;
			align-items: flex-start;
			gap: 10px;
			padding: 8px 0;
			border-bottom: 1px solid #f0f0f1;
		}
		.mbs-related-auto-row:last-child { border-bottom: none; }
		.mbs-related-auto-label {
			flex: 0 0 90px;
			font-weight: 600;
			font-size: 12px;
			color: #50575e;
			padding-top: 4px;
			text-transform: uppercase;
			letter-spacing: 0.04em;
		}
		.mbs-related-auto-thumbs {
			display: flex;
			flex-wrap: wrap;
			gap: 8px;
			flex: 1;
		}
		.mbs-related-thumb-link {
			display: block;
			text-decoration: none;
			border-radius: 3px;
			overflow: hidden;
			transition: box-shadow 0.12s ease, transform 0.12s ease;
		}
		.mbs-related-thumb-link:hover {
			box-shadow: 0 2px 8px rgba(0,0,0,0.18);
			transform: translateY(-1px);
		}
		.mbs-related-thumb-img {
			display: block;
			height: 72px;
			width: auto;
			max-width: 52px;
			object-fit: cover;
			border: 1px solid #dcdcde;
			border-radius: 3px;
		}
		.mbs-related-thumb-placeholder {
			display: flex;
			align-items: center;
			justify-content: center;
			height: 72px;
			width: 48px;
			font-size: 10px;
			line-height: 1.2;
			text-align: center;
			color: #787c82;
			background: #f6f7f7;
			border: 1px dashed #c3c4c7;
			border-radius: 3px;
			padding: 4px;
			word-break: break-word;
		}
		.mbs-related-section-heading {
			font-size: 13px;
			font-weight: 600;
			color: #1d2327;
			margin: 12px 0 6px;
			padding: 0;
		}
		.mbs-related-onix-section { margin-top: 4px; }
		.mbs-related-auto-empty {
			color: #787c82;
			font-style: italic;
			margin-bottom: 8px;
		}
		</style>
		<?php

		if ( $has_any ) {
			?>
			<div class="mbs-related-auto-panel">
				<?php
				$this->render_auto_group( __( 'Series',     'my-book-showroom' ), $series_posts );
				$this->render_auto_group( __( 'Author',     'my-book-showroom' ), $author_posts );
				$this->render_auto_group( __( 'Collection', 'my-book-showroom' ), $collection_posts );
				$this->render_auto_group( __( 'Universe',   'my-book-showroom' ), $universe_posts );
				?>
			</div>
			<?php
		} else {
			echo '<p class="mbs-related-auto-empty description">'
				. esc_html__( 'No other books in this catalog share the same Series, Author, Collection, or Universe.', 'my-book-showroom' )
				. '</p>';
		}

		echo '<hr style="margin:10px 0 14px;">';
	}

	/**
	 * Query posts sharing at least one term in $taxonomy with $post_id.
	 * Excludes $post_id itself. Returns up to 40 posts ordered by title.
	 *
	 * @param int    $post_id
	 * @param string $taxonomy
	 * @return WP_Post[]
	 */
	private function get_related_by_tax( int $post_id, string $taxonomy ): array {
		$term_ids = wp_get_object_terms( $post_id, $taxonomy, array( 'fields' => 'ids' ) );
		if ( is_wp_error( $term_ids ) || empty( $term_ids ) ) {
			return array();
		}
		$posts = get_posts( array(
			'post_type'      => MBS_POST_TYPE,
			'post_status'    => array( 'publish', 'draft', 'pending', 'future' ),
			'post__not_in'   => array( $post_id ), // phpcs:ignore WordPressVIPMinimum.Performance.WPQueryParams.PostNotIn_post__not_in -- standard WP query pattern; VIP rule does not apply to this plugin
			'posts_per_page' => 40,
			'orderby'        => 'title',
			'order'          => 'ASC',
			'tax_query'      => array(  // phpcs:ignore WordPress.DB.SlowDBQuery
				array(
					'taxonomy' => $taxonomy,
					'field'    => 'term_id',
					'terms'    => $term_ids,
					'operator' => 'IN',
				),
			),
		) );
		return is_array( $posts ) ? $posts : array();
	}

	/**
	 * Render one auto-detected group row (label + book cover thumbnails).
	 * Silently skips if $posts is empty.
	 *
	 * @param string    $label  Group label, e.g. "Series".
	 * @param WP_Post[] $posts  Related posts for this group.
	 */
	private function render_auto_group( string $label, array $posts ): void {
		if ( empty( $posts ) ) {
			return;
		}
		?>
		<div class="mbs-related-auto-row">
			<span class="mbs-related-auto-label"><?php echo esc_html( $label ); ?></span>
			<div class="mbs-related-auto-thumbs">
				<?php foreach ( $posts as $p ) :
					$thumb    = get_the_post_thumbnail_url( $p->ID, 'thumbnail' ); // R5-exempt: admin metabox context
					$edit_url = get_edit_post_link( $p->ID );
					$title    = get_the_title( $p );
				?>
					<a href="<?php echo esc_url( $edit_url ); ?>"
					   title="<?php echo esc_attr( $title ); ?>"
					   class="mbs-related-thumb-link">
						<?php if ( $thumb ) : ?>
							<img src="<?php echo esc_url( $thumb ); ?>"
							     alt="<?php echo esc_attr( $title ); ?>"
							     class="mbs-related-thumb-img">
						<?php else : ?>
							<span class="mbs-related-thumb-placeholder"><?php echo esc_html( $title ); ?></span>
						<?php endif; ?>
					</a>
				<?php endforeach; ?>
			</div>
		</div>
		<?php
	}

	// ─────────────────────────────────────────────────────────────────────────
	// Private: single manual ONIX row
	// ─────────────────────────────────────────────────────────────────────────

	/**
	 * Render a single related product row.
	 * $row is null when rendering the template clone.
	 *
	 * @param object|null $row  Existing row, or null for JS clone template.
	 */
	private function render_row( ?object $row ): void {
		static $idx = 0;
		$is_template = ( null === $row );
		$row_id      = $is_template ? '' : (int) $row->id;
		$isbn        = $is_template ? '' : esc_attr( $row->related_isbn ?? '' );
		$rel_code    = $is_template ? '' : esc_attr( $row->relation_code ?? '' );
		$rel_format  = $is_template ? '' : esc_attr( $row->related_format ?? '' );
		$sort        = $is_template ? 0  : (int) ( $row->sort_order ?? 0 );
		$key         = $is_template ? '__IDX__' : $idx;
		?>
		<div class="mbs-related-product-row" style="display:flex;gap:8px;align-items:center;margin-bottom:6px;flex-wrap:wrap;">
			<input type="hidden" name="mbs_rp_id[<?php echo esc_attr( $key ); ?>]" value="<?php echo esc_attr( $row_id ); ?>">
			<input type="hidden" name="mbs_rp_sort[<?php echo esc_attr( $key ); ?>]" value="<?php echo esc_attr( $sort ); ?>">
			<input type="text"
			       name="mbs_rp_isbn[<?php echo esc_attr( $key ); ?>]"
			       value="<?php echo esc_attr( $isbn ); ?>"
			       class="regular-text"
			       placeholder="<?php esc_attr_e( 'Related ISBN-13', 'my-book-showroom' ); ?>">
			<select name="mbs_rp_code[<?php echo esc_attr( $key ); ?>]">
				<option value=""><?php esc_html_e( '-- Relation --', 'my-book-showroom' ); ?></option>
				<?php foreach ( MBS_Related_Products_DAO::ALLOWED_RELATION_CODES as $code ) : ?>
					<option value="<?php echo esc_attr( $code ); ?>" <?php selected( $rel_code, $code ); ?>>
						<?php echo esc_html( $code . ' -- ' . MBS_Related_Products_DAO::relation_label( $code ) ); ?>
					</option>
				<?php endforeach; ?>
			</select>
			<input type="text"
			       name="mbs_rp_format[<?php echo esc_attr( $key ); ?>]"
			       value="<?php echo esc_attr( $rel_format ); ?>"
			       class="small-text"
			       placeholder="<?php esc_attr_e( 'Format (opt.)', 'my-book-showroom' ); ?>">
			<button type="button" class="button mbs-remove-related-product">
				<?php esc_html_e( 'Remove', 'my-book-showroom' ); ?>
			</button>
		</div>
		<?php
		if ( ! $is_template ) {
			$idx++;
		}
	}

	// ─────────────────────────────────────────────────────────────────────────
	// Save
	// ─────────────────────────────────────────────────────────────────────────

	public function save( int $post_id, WP_Post $post ): void {
		if ( ! $this->verify_save( $post_id, 'mbs_related_products_nonce', 'mbs_related_products_save' ) ) {
			return;
		}

		// PRO/PUB gate — server-side canonical enforcement.
		if ( ! mbs_is_pro() ) {
			return;
		}

		$rp_isbns   = wp_unslash( (array) ( $_POST['mbs_rp_isbn']   ?? array() ) );
		$rp_codes   = wp_unslash( (array) ( $_POST['mbs_rp_code']   ?? array() ) );
		$rp_formats = wp_unslash( (array) ( $_POST['mbs_rp_format'] ?? array() ) );
		$rp_sorts   = (array) wp_unslash( $_POST['mbs_rp_sort'] ?? array() );

		// Delete all existing rows then re-insert the clean set.
		MBS_Related_Products_DAO::delete_for_post( $post_id );

		foreach ( $rp_isbns as $key => $isbn_raw ) {
			$isbn = MBS_ISBN_Helper::normalize_isbn( sanitize_text_field( $isbn_raw ) );
			if ( false === $isbn ) {
				continue; // Skip invalid ISBNs.
			}
			$code = sanitize_text_field( $rp_codes[ $key ] ?? '' );
			if ( ! in_array( $code, MBS_Related_Products_DAO::ALLOWED_RELATION_CODES, true ) ) {
				continue; // Skip invalid relation codes.
			}
			$related_format = sanitize_key( $rp_formats[ $key ] ?? '' ) ?: null;
			$sort_order     = absint( $rp_sorts[ $key ] ?? $key );
			MBS_Related_Products_DAO::upsert( $post_id, $code, $isbn, $related_format, $sort_order );
		}
	}

	/**
	 * Render one editor section within the unified book editor shell.
	 *
	 * Passthrough — single-section handler (Sprint 169).
	 *
	 * @since  1.0.0
	 * @param  string  $section_key  Section key (ignored for single-section handlers).
	 * @param  WP_Post $post         Current post object.
	 * @return void
	 */
	public function render_section( string $section_key, WP_Post $post ): void {
		$this->render( $post );
	}

	/**
	 * Save one editor section within the unified book editor shell.
	 *
	 * Passthrough — single-section handler (Sprint 169).
	 *
	 * @since  1.0.0
	 * @param  string  $section_key  Section key (ignored for single-section handlers).
	 * @param  int     $post_id      Post ID.
	 * @param  WP_Post $post         Post object.
	 * @return void
	 */
	public function save_section( string $section_key, int $post_id, WP_Post $post ): void {
		$this->save( $post_id, $post );
	}

}
