<?php
/**
 * File Path: includes/metaboxes/class-mbs-metabox-series-collections.php
 *
 * Series & Collections Handler — My Book Showroom
 *
 * Single-responsibility handler for all series and collection taxonomy
 * fields on the mbs_book edit screen. Extracted from MBS_Metabox_Book_Details
 * (Sprint 178) when the Book Details tab was restructured.
 *
 * Fields owned:
 *   mbs_series_term_id                   — single-select series taxonomy term
 *   mbs_series_position                  — position within series (text, decimals ok)
 *   mbs_series_reading_order_position    — suggested narrative reading order position
 *   mbs_collection_ids[]                 — many-to-many collection taxonomy assignments
 *   mbs_collection_position_{id}         — per-collection position (MBS_Book_Collection_Position_DAO)
 *
 * Nonce: mbs_series_collections_save / mbs_series_collections_nonce
 *
 * @package    My_Book_Showroom
 * @subpackage Includes/Metaboxes
 * @since      1.0.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_Series_Collections 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
	// -------------------------------------------------------------------------

	/**
	 * Render series and collection fields.
	 *
	 * @param WP_Post $post Current post object.
	 * @return void
	 */
	public function render( WP_Post $post ): void {
		wp_nonce_field( 'mbs_series_collections_save', 'mbs_series_collections_nonce' );

		$series_position = get_post_meta( $post->ID, 'mbs_series_position', true );
		// PLG-009: mbs_series_reading_order_position is stored in mbs_book_meta (Mig-4.34),
		// not in wp_postmeta. Must read via DAO, not get_post_meta().
		$sro_position    = MBS_Book_Meta_DAO::get( $post->ID, 'mbs_series_reading_order_position', '' );
		$series_ro_hidden = (int) MBS_Book_Meta_DAO::get( $post->ID, 'mbs_series_reading_order_hidden', 0 );

		// PLG-055: Resolve current series term for the single-select dropdown.
		$series_terms_on_book = wp_get_object_terms( $post->ID, MBS_TAX_SERIES );
		$current_series_id    = ( ! is_wp_error( $series_terms_on_book ) && ! empty( $series_terms_on_book ) )
			? (int) $series_terms_on_book[0]->term_id
			: 0;

		// F-018: Fetch all series terms ordered by parent; build parent → children map.
		$all_series_terms = get_terms( array(
			'taxonomy'   => MBS_TAX_SERIES,
			'hide_empty' => false,
			'orderby'    => 'parent',
			'order'      => 'ASC',
		) );
		$series_top_level = array();
		$series_children  = array();
		if ( ! is_wp_error( $all_series_terms ) ) {
			foreach ( $all_series_terms as $_st ) {
				if ( 0 === (int) $_st->parent ) {
					$series_top_level[] = $_st;
				} else {
					$series_children[ (int) $_st->parent ][] = $_st;
				}
			}
		}

		// F-016: Collections — many-to-many checkbox picker.
		$assigned_collection_ids = array();
		$assigned_col_terms      = wp_get_object_terms( $post->ID, MBS_TAX_COLLECTION, array( 'fields' => 'ids' ) );
		if ( ! is_wp_error( $assigned_col_terms ) ) {
			$assigned_collection_ids = array_map( 'intval', $assigned_col_terms );
		}
		$all_collections = get_terms( array( 'taxonomy' => MBS_TAX_COLLECTION, 'hide_empty' => false ) );
		?>
		<table class="form-table mbs-series-collections-table">

			<?php /* ── Series ─────────────────────────────────────────────────── */ ?>
			<tr>
				<th scope="row">
					<label for="mbs_series_term_id"><?php esc_html_e( 'Series', 'my-book-showroom' ); ?></label>
				</th>
				<td>
					<select id="mbs_series_term_id" name="mbs_series_term_id">
						<option value="0"><?php esc_html_e( '— Not part of a series —', 'my-book-showroom' ); ?></option>
						<?php foreach ( $series_top_level as $series_term ) :
							$tid = (int) $series_term->term_id;
						?>
							<option value="<?php echo esc_attr( $tid ); ?>"<?php selected( $current_series_id, $tid ); ?>>
								<?php echo esc_html( $series_term->name ); ?>
							</option>
							<?php if ( isset( $series_children[ $tid ] ) ) : ?>
								<?php foreach ( $series_children[ $tid ] as $child_term ) : ?>
									<option value="<?php echo esc_attr( $child_term->term_id ); ?>"<?php selected( $current_series_id, (int) $child_term->term_id ); ?>>
										&nbsp;&nbsp;&mdash; <?php echo esc_html( $child_term->name ); ?>
									</option>
								<?php endforeach; ?>
							<?php endif; ?>
						<?php endforeach; ?>
					</select>
					<p class="description"><?php esc_html_e( 'A book can belong to at most one series.', 'my-book-showroom' ); ?></p>
					<div id="mbs-create-series-wrap" style="margin-top:8px; display:flex; gap:6px; align-items:center; flex-wrap:wrap;">
						<input type="text" id="mbs-new-series-name" class="regular-text"
						       placeholder="<?php esc_attr_e( 'New series name…', 'my-book-showroom' ); ?>"
						       style="max-width:260px;" autocomplete="off">
						<button type="button" id="mbs-create-series-btn" class="button">
							<?php esc_html_e( 'Create Series', 'my-book-showroom' ); ?>
						</button>
						<span id="mbs-create-series-msg" style="display:none; font-size:13px;"></span>
					</div>
				</td>
			</tr>
			<?php
			wp_add_inline_script( 'mbs-book-editor', <<<'JS'
(function($){
    $('#mbs-create-series-btn').on('click', function(){
        var name = $('#mbs-new-series-name').val().trim();
        var $msg = $('#mbs-create-series-msg');
        if (!name) {
            $msg.css('color','#d63638').text('Please enter a series name.').show();
            return;
        }
        var nonce = (window.MBS_BookEditorAjax || {}).nonce || '';
        $msg.css('color','#646970').text('Creating…').show();
        $.post(ajaxurl, {
            action: 'mbs_create_series_term',
            nonce:  nonce,
            name:   name
        }, function(res){
            if (!res.success) {
                $msg.css('color','#d63638').text(res.data || 'Error.').show();
                return;
            }
            var d = res.data;
            var label = d.existed ? '"' + d.name + '" already exists — selected.' : '"' + d.name + '" created and selected.';
            var $sel = $('#mbs_series_term_id');
            if (!$sel.find('option[value="' + d.term_id + '"]').length) {
                $sel.append($('<option>', {value: d.term_id, text: d.name}));
            }
            $sel.val(d.term_id);
            $msg.css('color','#008a00').text(label).show();
            $('#mbs-new-series-name').val('');
        }).fail(function(){
            $msg.css('color','#d63638').text('Server error. Please try again.').show();
        });
    });
    $('#mbs-new-series-name').on('keydown', function(e){
        if (e.key === 'Enter') { e.preventDefault(); $('#mbs-create-series-btn').trigger('click'); }
    });
})(jQuery);
JS
			);
			?>

			<?php /* ── Series Position ────────────────────────────────────────── */ ?>
			<tr>
				<th scope="row">
					<label for="mbs_series_position"><?php esc_html_e( 'Series Position', 'my-book-showroom' ); ?></label>
				</th>
				<td>
					<input type="text" id="mbs_series_position" name="mbs_series_position"
					       class="small-text" value="<?php echo esc_attr( $series_position ); ?>"
					       placeholder="e.g. 1, 2, 0.5" autocomplete="off" />
					<p class="description"><?php esc_html_e( 'Position within the series (e.g. "1", "2", "0.5" for a prequel). Leave blank if not part of a series.', 'my-book-showroom' ); ?></p>
				</td>
			</tr>

			<?php /* ── Suggested Reading Order ──────────────────────────────── */ ?>
			<tr>
				<th scope="row">
					<span class="mbs-label-tip-pair"><label for="mbs_series_reading_order_position"><?php esc_html_e( 'Suggested Reading Order', 'my-book-showroom' ); ?></label><?php mbs_help_tip(
					__( 'Series Position is the publisher\'s release-order number (Book 1, 2, 3). Suggested Reading Order is a separate sequence — the order you recommend readers experience the books, which may differ. Example: a prequel published as Book 5 might have a Reading Order position of 0.5, placing it between Books 0 and 1 in the narrative sequence. Leave blank to inherit the Series Position value.', 'my-book-showroom' ),
					__( 'Series Position vs Reading Order', 'my-book-showroom' ),
					'mbs-help-tip--wide'
				); ?></span>
				</th>
				<td>
					<input type="text" id="mbs_series_reading_order_position" name="mbs_series_reading_order_position"
					       class="small-text" value="<?php echo esc_attr( $sro_position ); ?>"
					       placeholder="e.g. 1, 2, 0.5" autocomplete="off" />
					<p class="description"><?php esc_html_e( 'Suggested narrative sequence position. Leave blank to use Series Position as the default. Use decimals for books that fall between whole positions (e.g. 1.5 for a story between Book 1 and Book 2).', 'my-book-showroom' ); ?></p>
				</td>
			</tr>

			<?php /* Sprint RO-Display 5b: Series RO suppress toggle */ ?>
			<tr>
				<th scope="row">
					<span class="mbs-label-tip-pair"><label for="mbs_series_reading_order_hidden"><?php esc_html_e( 'Suppress Reading Order', 'my-book-showroom' ); ?></label><?php mbs_help_tip(
					__( 'Hides the Reading Order row for this specific book even when Reading Order is enabled globally. Use this for books that belong to a series for discoverability purposes but do not fit naturally into the narrative sequence — such as companion novels, short story anthologies, or author\' s notes volumes.', 'my-book-showroom' ),
					__( 'About Suppress Reading Order', 'my-book-showroom' ),
					'mbs-help-tip--wide'
				); ?></span>
				</th>
				<td>
					<?php
					$_mbs_sro_global_on = '1' === get_option( 'mbs_setting_show_series_reading_order', '0' );
					?>
					<label>
						<input type="checkbox"
						       id="mbs_series_reading_order_hidden"
						       name="mbs_series_reading_order_hidden"
						       value="1"
						       <?php checked( 1, $series_ro_hidden ); ?>
						       <?php disabled( ! $_mbs_sro_global_on, true ); ?>>
						<?php esc_html_e( 'Suppress reading order display for this book', 'my-book-showroom' ); ?>
					</label>
					<p class="description">
						<?php if ( ! $_mbs_sro_global_on ) : ?>
							<?php esc_html_e( 'Series Reading Order is currently disabled globally. Enable Show Series Reading Order in Settings -> General -> Display Options to use this setting.', 'my-book-showroom' ); ?>
						<?php else : ?>
							<?php esc_html_e( 'When checked, the Reading Order row is hidden for this book on single-book pages, even if Reading Order is enabled globally.', 'my-book-showroom' ); ?>
						<?php endif; ?>
					</p>
				</td>
			</tr>

			<?php /* ── Collections ─────────────────────────────────────────── */ ?>
			<tr>
				<th scope="row">
					<?php if ( ! is_wp_error( $all_collections ) && ! empty( $all_collections ) ) : ?>
						<button type="button"
						        class="button-link mbs-col-toggle"
						        aria-expanded="<?php echo ! empty( $assigned_collection_ids ) ? 'true' : 'false'; ?>"
						        aria-controls="mbs-collections-body">
							<span class="mbs-col-chevron" aria-hidden="true"><?php echo ! empty( $assigned_collection_ids ) ? '&#9660;' : '&#9654;'; ?></span>
							<?php esc_html_e( 'Collections', 'my-book-showroom' ); ?>
							<span class="mbs-col-count"><?php
								$_col_count = count( $assigned_collection_ids );
								echo $_col_count > 0 // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- integer count() value; esc_html__() escapes the label
									? '(' . $_col_count . ' ' . esc_html__( 'selected', 'my-book-showroom' ) . ')' // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- static HTML wrapper + integer count; esc_html__() escapes the label
									: esc_html__( '(none)', 'my-book-showroom' );
							?></span>
						</button>
					<?php else : ?>
						<?php esc_html_e( 'Collections', 'my-book-showroom' ); ?>
					<?php endif; ?>
				</th>
				<td>
					<?php if ( is_wp_error( $all_collections ) || empty( $all_collections ) ) : ?>
						<p class="description">
							<?php esc_html_e( 'No collections exist yet.', 'my-book-showroom' ); ?>
							<a href="<?php echo esc_url( admin_url( 'edit-tags.php?taxonomy=' . MBS_TAX_COLLECTION . '&post_type=' . MBS_POST_TYPE ) ); ?>">
								<?php esc_html_e( 'Add a collection →', 'my-book-showroom' ); ?>
							</a>
						</p>
					<?php else : ?>
						<div id="mbs-collections-body" <?php echo empty( $assigned_collection_ids ) ? 'hidden' : ''; ?>>
							<div id="mbs-collections-list">
								<?php foreach ( $all_collections as $col_term ) :
									$col_id     = (int) $col_term->term_id;
									$is_checked = in_array( $col_id, $assigned_collection_ids, true );
									$saved_pos  = MBS_Book_Collection_Position_DAO::get_position( $post->ID, $col_id );
									$type_label = MBS_Taxonomy_Collection::get_type_label(
									              (string) get_term_meta( $col_id, 'mbs_collection_type', true ) );
								?>
									<div class="mbs-collection-item" style="margin-bottom:6px; display:flex; align-items:center; gap:10px; flex-wrap:wrap;">
										<label style="display:flex; align-items:center; gap:6px; min-width:200px;">
											<input type="checkbox"
											       class="mbs-collection-checkbox"
											       name="mbs_collection_ids[]"
											       value="<?php echo esc_attr( $col_id ); ?>"
											       <?php checked( $is_checked ); ?> />
											<?php echo esc_html( $col_term->name ); ?>
											<?php if ( $type_label ) : ?>
												<span style="color:#646970; font-size:11px;">(<?php echo esc_html( $type_label ); ?>)</span>
											<?php endif; ?>
										</label>
										<span class="mbs-collection-position-wrap" <?php echo $is_checked ? '' : 'style="display:none;"'; ?>>
											<label style="font-size:12px; color:#646970;">
												<?php esc_html_e( 'Position:', 'my-book-showroom' ); ?>
												<input type="number"
												       name="mbs_collection_position_<?php echo esc_attr( $col_id ); ?>"
												       value="<?php echo esc_attr( $saved_pos ); ?>"
												       min="1"
												       style="width:60px;"
												       placeholder="—" />
											</label>
										</span>
									</div>
								<?php endforeach; ?>
							</div>
							<p class="description" style="margin-top:6px;">
								<?php esc_html_e( 'A book may belong to multiple collections. Position sets reading order within each collection.', 'my-book-showroom' ); ?>
							</p>
						</div>
					<?php endif; ?>
				</td>
			</tr>

			<script>
			(function($){
				function mbsToggleCollectionPos(cb) {
					var $wrap = $(cb).closest('.mbs-collection-item').find('.mbs-collection-position-wrap');
					if (cb.checked) { $wrap.show(); } else { $wrap.hide().find('input[type=number]').val(''); }
				}
				$(document).on('change', '.mbs-collection-checkbox', function(){ mbsToggleCollectionPos(this); });
				var colBtn  = document.querySelector('.mbs-col-toggle');
				var colBody = document.getElementById('mbs-collections-body');
				if (colBtn && colBody) {
					var colChev  = colBtn.querySelector('.mbs-col-chevron');
					var colCount = colBtn.querySelector('.mbs-col-count');
					function updateColCount() {
						var n = colBody.querySelectorAll('.mbs-collection-checkbox:checked').length;
						colCount.textContent = n > 0 ? '(' + n + ' selected)' : '(none)';
					}
					colBtn.addEventListener('click', function() {
						var open = colBtn.getAttribute('aria-expanded') === 'true';
						if (open) {
							colBody.setAttribute('hidden', '');
							colBtn.setAttribute('aria-expanded', 'false');
							colChev.innerHTML = '&#9654;';
						} else {
							colBody.removeAttribute('hidden');
							colBtn.setAttribute('aria-expanded', 'true');
							colChev.innerHTML = '&#9660;';
						}
					});
					colBody.addEventListener('change', updateColCount);
				}
			})(jQuery);
			</script>

		</table>
		<?php
	}

	// -------------------------------------------------------------------------
	// Save
	// -------------------------------------------------------------------------

	/**
	 * Save series and collection fields.
	 *
	 * @param int     $post_id Post ID.
	 * @param WP_Post $post    Post object.
	 * @return void
	 */
	public function save( int $post_id, WP_Post $post ): void {
		if ( ! $this->verify_save( $post_id, 'mbs_series_collections_nonce', 'mbs_series_collections_save' ) ) {
			return;
		}

		// PLG-055: Series taxonomy assignment (single-select enforcement).
		if ( isset( $_POST['mbs_series_term_id'] ) ) {
			if ( is_array( $_POST['mbs_series_term_id'] ) ) {
				set_transient( 'mbs_series_save_error_' . $post_id, 1, 30 );
			} else {
				$term_id = (int) sanitize_text_field( wp_unslash( $_POST['mbs_series_term_id'] ) );
				if ( 0 === $term_id ) {
					wp_set_object_terms( $post_id, array(), MBS_TAX_SERIES );
				} elseif ( term_exists( $term_id, MBS_TAX_SERIES ) ) {
					wp_set_object_terms( $post_id, array( $term_id ), MBS_TAX_SERIES );
				}
			}
		}

		// Series Position.
		$series_pos_raw = isset( $_POST['mbs_series_position'] ) ? wp_unslash( $_POST['mbs_series_position'] ) : '';
		update_post_meta( $post_id, 'mbs_series_position', sanitize_text_field( $series_pos_raw ) );

		// Suggested Reading Order Position (Sprint RO-Display 5a — stored in mbs_book_meta).
		$sro_raw = isset( $_POST['mbs_series_reading_order_position'] )
			? (string) wp_unslash( $_POST['mbs_series_reading_order_position'] )
			: '';
		$sro_pos = ( '' === trim( $sro_raw ) ) ? null : max( 0.0, (float) $sro_raw );
		MBS_Book_Meta_DAO::set_series_reading_order_position( $post_id, $sro_pos );

		// Series Reading Order suppress (Sprint RO-Display 5b).
		$series_ro_hidden = isset( $_POST['mbs_series_reading_order_hidden'] ) ? 1 : 0;
		MBS_Book_Meta_DAO::set_series_reading_order_hidden( $post_id, $series_ro_hidden );

		// F-016: Collection taxonomy assignments (many-to-many) + per-collection positions.
		$submitted_collection_ids = array();
		if ( isset( $_POST['mbs_collection_ids'] ) && is_array( $_POST['mbs_collection_ids'] ) ) {
			foreach ( $_POST['mbs_collection_ids'] as $raw_id ) {
				$col_id = absint( $raw_id );
				if ( $col_id > 0 && term_exists( $col_id, MBS_TAX_COLLECTION ) ) {
					$submitted_collection_ids[] = $col_id;
				}
			}
		}
		wp_set_object_terms( $post_id, $submitted_collection_ids, MBS_TAX_COLLECTION );

		$all_col_terms = get_terms( array( 'taxonomy' => MBS_TAX_COLLECTION, 'hide_empty' => false, 'fields' => 'ids' ) );
		if ( ! is_wp_error( $all_col_terms ) ) {
			foreach ( $all_col_terms as $col_id ) {
				$col_id    = (int) $col_id;
				$pos_field = 'mbs_collection_position_' . $col_id;
				if ( in_array( $col_id, $submitted_collection_ids, true ) && isset( $_POST[ $pos_field ] ) ) {
					$position = absint( $_POST[ $pos_field ] );
					if ( $position > 0 ) {
						MBS_Book_Collection_Position_DAO::upsert( $post_id, $col_id, $position );
					} else {
						MBS_Book_Collection_Position_DAO::delete( $post_id, $col_id );
					}
				} else {
					MBS_Book_Collection_Position_DAO::delete( $post_id, $col_id );
				}
			}
		}
	}

	// -------------------------------------------------------------------------
	// Editor shell interface
	// -------------------------------------------------------------------------

	/**
	 * Render one editor section within the unified book editor shell.
	 *
	 * @since  1.0.0
	 * @param  string  $section_key Section key (unused — single-section handler).
	 * @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.
	 *
	 * @since  1.0.0
	 * @param  string  $section_key Section key (unused — single-section handler).
	 * @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 );
	}

}
