<?php
/**
 * File Path: includes/admin/tabs/authors-series/class-mbs-tab-series.php
 *
 * Series Tab — Save & Render
 *
 * Top-level handler for the Series tab.
 *
 * Round 3 scope (this file): card-based visual redesign matching the
 * Authors tab; removal of the never-consumed mbs_series_sort_order field
 * (PLG-052); introduction of the spec'd mbs_series_reading_order feature
 * with drag-and-drop UI; introduction of mbs_series_default_sort per-series
 * toggle. The mbs_series_complete field is retained per decision #4 and
 * will be documented in Round 5's Doc 1a amendment.
 *
 * UI model (matches Authors tab pattern from Round 2):
 *     One top-level <form> per series, each wrapping a native <details>/
 *     <summary> element. Compact collapsed header; full edit surface only
 *     appears on expand. Auto-expands the just-saved card after PRG
 *     redirect via a 'saved' query arg.
 *
 * Owned term_meta keys:
 *
 *   · mbs_series_reading_order   — JSON-encoded array of book post IDs in
 *                                  recommended reading sequence.
 *                                  Managed via MBS_Series_Reading_Order_DAO.
 *   · mbs_series_default_sort    — 'series_number' | 'reading_order'.
 *                                  Set by the admin per series; read by
 *                                  Context 3 consumers to decide which
 *                                  ordering to apply when displaying the
 *                                  series's books (F-item — no live
 *                                  consumer yet; data is persisted so the
 *                                  future consumer has what it needs).
 *   · mbs_series_complete        — '1' | '0'. Boolean "series is complete".
 *                                  Consumer is a future enhanced series
 *                                  template (F-item).
 *   · mbs_term_layout            — Layout slug override. Empty = use global
 *                                  default. FREE: clamped to FREE slugs.
 *                                  TLA-001.
 *
 * Series description is stored via wp_update_term() on the term itself —
 * it is a core taxonomy column, not term_meta.
 *
 * Removed in Round 3:
 *   · mbs_series_sort_order — PLG-052. Never had a consumer; all live data
 *                              was 0 per admin-screen evidence. save()
 *                              deletes any leftover rows on first save
 *                              after the upgrade as a one-time cleanup.
 *
 * Tab slug: 'series'
 * Nonce:    mbs_series_save / mbs_series_nonce
 *
 * @package    My_Book_Showroom
 * @subpackage Includes/Admin/Tabs/Authors-Series
 * @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_Tab_Series extends MBS_Tab_Base {

	public function __construct() {
		add_action( 'admin_init', array( $this, 'handle_save' ) );
		add_action( 'admin_init', array( $this, 'save_global_settings' ) );
	}

	// -------------------------------------------------------------------------
	// Save accessor overrides — DUP-005 (handle_save() now in MBS_Tab_Base)
	// -------------------------------------------------------------------------

	/** @return string */
	protected function save_nonce_field(): string { return 'mbs_series_nonce'; }
	/** @return string */
	protected function save_nonce_action(): string { return 'mbs_series_save'; }
	/** @return string */
	protected function save_tab_slug(): string { return 'series'; }
	/** @return string */
	protected function save_post_id_field(): string { return 'mbs_save_series_id'; }

	/**
	 * Save extended series fields for a single mbs_series term.
	 *
	 * Called by handle_save() after nonce verification and capability check.
	 * Also callable directly from unit tests that bypass the security layer
	 * to exercise data-persistence behavior in isolation.
	 *
	 * @param int $term_id Validated, absint-cast term ID.
	 * @return void
	 */
	public function save( int $term_id ): void {
		if ( ! $term_id || ! term_exists( $term_id, MBS_TAX_SERIES ) ) {
			return;
		}

		// ── Description — stored on the term itself (not term_meta) ──────────
		$description = isset( $_POST['mbs_series_description'][ $term_id ] )
			? sanitize_textarea_field( wp_unslash( $_POST['mbs_series_description'][ $term_id ] ) )
			: '';
		wp_update_term( $term_id, MBS_TAX_SERIES, array( 'description' => $description ) );

		// ── Archive display mode (per-series override, Sprint RO-Display 4e) ──
		$allowed_disp  = array( '', 'order', 'reading_order', 'both' );
		$disp_mode_raw = isset( $_POST['mbs_series_archive_display_mode'][ $term_id ] )
			? sanitize_key( wp_unslash( $_POST['mbs_series_archive_display_mode'][ $term_id ] ) )
			: '';
		$disp_mode = in_array( $disp_mode_raw, $allowed_disp, true ) ? $disp_mode_raw : '';
		if ( '' === $disp_mode ) {
			delete_term_meta( $term_id, 'mbs_series_archive_display_mode' );
		} else {
			update_term_meta( $term_id, 'mbs_series_archive_display_mode', $disp_mode );
		}

		// ── Complete flag — unchecked checkbox absent from POST, store '0' ──
		$complete = isset( $_POST['mbs_series_complete'][ $term_id ] ) ? '1' : '0';
		update_term_meta( $term_id, 'mbs_series_complete', $complete );


		// ── Book Grid Layout (TLA-001) — extracted to MBS_Tab_Base::save_layout_field()
		$this->save_layout_field( $term_id );
		// ── Reading order (JSON-encoded array of book post IDs) ───────────────
		// The hidden input is populated by the card's JS either at form submit
		// time or after a drag. When the card has never been saved and the
		// admin hits Save without dragging, the JS still fills the input with
		// the currently-displayed default order — which locks that order in.
		$raw_order = isset( $_POST['mbs_series_reading_order'][ $term_id ] )
			? (string) wp_unslash( $_POST['mbs_series_reading_order'][ $term_id ] )
			: '';
		if ( '' !== $raw_order ) {
			$decoded = json_decode( $raw_order, true );
			if ( is_array( $decoded ) ) {
				MBS_Series_Reading_Order_DAO::save_reading_order( $term_id, $decoded );
			}
		}

		// ── Series banner image (F-011) — DUP-006: delegate to MBS_Tab_Base ──
		$this->save_banner_field( $term_id, 'mbs_series_banner_id', 'mbs_series_banner_id' );

		// ── One-time cleanup of the retired mbs_series_sort_order meta ───────
		// PLG-052: removed Round 3. All live values were 0 per admin-screen
		// evidence; this delete_term_meta is defensive and runs on every save.
		delete_term_meta( $term_id, 'mbs_series_sort_order' );
	}

	/**
	 * Save global Series archive display mode option.
	 *
	 * Handles the standalone global settings form on the Series sub-tab.
	 * Hooked to admin_init; exits via wp_safe_redirect (PRG pattern).
	 *
	 * @return void
	 */
	public function save_global_settings(): void {
		if ( ! isset( $_POST['mbs_series_global_nonce'] ) ) {
			return;
		}
		if ( ! wp_verify_nonce(
			sanitize_text_field( wp_unslash( $_POST['mbs_series_global_nonce'] ) ),
			'mbs_series_global_save'
		) ) {
			return;
		}
		if ( ! current_user_can( 'manage_options' ) ) {
			return;
		}
		$allowed_modes    = array( 'order', 'reading_order', 'both' );
		$series_disp_mode = isset( $_POST['mbs_setting_series_archive_display_mode'] )
			? sanitize_key( wp_unslash( $_POST['mbs_setting_series_archive_display_mode'] ) )
			: 'order';
		$series_disp_mode = in_array( $series_disp_mode, $allowed_modes, true ) ? $series_disp_mode : 'order';
		update_option( 'mbs_setting_series_archive_display_mode', $series_disp_mode );
		wp_safe_redirect( add_query_arg( array(
			'page'    => 'my-book-showroom-settings',
			'tab'     => 'series',
			'updated' => '1',
		), admin_url( 'admin.php' ) ) );
		exit;
	}

	// -------------------------------------------------------------------------
	// Render
	// -------------------------------------------------------------------------

	/**
	 * Enqueue Series tab assets when this tab is active.
	 *
	 * @param string $current_tab The active tab slug.
	 * @return void
	 */
	public function enqueue( string $current_tab ): void {
		if ( 'series' !== $current_tab ) {
			return;
		}

		$this->enqueue_entity_base_assets();

		wp_enqueue_style(
			'mbs-admin-settings-series',
			plugins_url( 'assets/css/mbs-admin-settings-series.css', MBS_DIR . 'bookshowroom.php' ),
			array( 'mbs-admin-settings' ),
			MBS_VERSION
		);

		// jQuery UI Sortable is already globally enqueued via mbs-book-editor
		// in MBS_Plugin::enqueue_admin_assets(); declared here as a dep for
		// safety (deps are idempotent).
		wp_enqueue_script(
			'mbs-admin-series',
			plugins_url( 'assets/js/adminjs/admin-series.js', MBS_DIR . 'bookshowroom.php' ),
			array( 'jquery', 'jquery-ui-sortable', 'mbs-admin-utils', 'wp-i18n' ),
			MBS_VERSION,
			true
		);
		wp_set_script_translations( 'mbs-admin-series', 'my-book-showroom', MBS_DIR . 'languages' );

		// Live filter + A-Z collapse/expand (shared with Authors tab).
		wp_enqueue_script(
			'mbs-admin-filter',
			plugins_url( 'assets/js/adminjs/mbs-admin-filter.js', MBS_DIR . 'bookshowroom.php' ),
			array(),
			MBS_VERSION,
			true
		);

		// F-SORT-2: Sort preference toggle.
		wp_enqueue_script(
			'mbs-admin-sort-pref',
			plugins_url( 'assets/js/adminjs/mbs-admin-sort-pref.js', MBS_DIR . 'bookshowroom.php' ),
			array(),
			MBS_VERSION,
			true
		);
		$this->localize_sort_pref( 'series', array( 'name', 'name_sort' ), 'name' );
	}

	/**
	 * Render the Series tab.
	 *
	 * Delegates to MBS_Tab_Series_Render::render().
	 * Kept as a thin instance method so class-mbs-settings.php can call
	 * $tab->render() without modification (R-15 delegate pattern).
	 *
	 * @return void
	 */
	public function render(): void {
		MBS_Tab_Series_Render::render();
	}

}
