<?php
/**
 * File Path: includes/admin/tabs/class-mbs-tab-base.php
 *
 * My Book Showroom — Settings Tab Base Class
 *
 * Non-abstract base for all Settings tab orchestrators. Subclasses override
 * only the methods they need; unoverridden stubs are safe no-ops.
 *
 * Migration: extend MBS_Tab_Base and set $this->tab_id in the constructor,
 * then replace the inline `$current_tab === 'slug'` guard in enqueue() with
 * `$this->is_active( $current_tab )`.
 *
 * @package MyBookShowroom
 * @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
class MBS_Tab_Base {

	/**
	 * The tab's URL slug, e.g. 'general', 'appearance'.
	 *
	 * Set this in the subclass constructor. Used by is_active().
	 *
	 * @var string
	 */
	protected string $tab_id = '';

	/**
	 * Return true when the given tab slug matches this tab's ID.
	 *
	 * Intended for use inside enqueue() to guard asset registration:
	 *
	 *   if ( ! $this->is_active( $current_tab ) ) { return; }
	 *
	 * @param string $current_tab Sanitized tab slug from the URL parameter.
	 * @return bool
	 */
	public function is_active( string $current_tab ): bool {
		return '' !== $this->tab_id && $current_tab === $this->tab_id;
	}

	/**
	 * Register settings fields via the WordPress Settings API.
	 *
	 * Hooked to admin_init. Override in tabs that register settings groups.
	 */
	public function register(): void {}

	/**
	 * Enqueue assets for this tab.
	 *
	 * Called for every settings page load; guard with is_active() to restrict
	 * to the relevant tab.
	 *
	 * @param string $current_tab Sanitized tab slug from the URL parameter.
	 */
	public function enqueue( string $current_tab ): void {}

	/**
	 * Render the tab's HTML content.
	 *
	 * Called by MBS_Settings when this tab is the active tab.
	 */
	public function render(): void {}

	// -------------------------------------------------------------------------
	// Entity-tab shared helpers — DUP-004 / DUP-005 / TAB-DUP-001
	// -------------------------------------------------------------------------

	/**
	 * Enqueue the base CSS assets shared by all entity tabs (Authors, Series,
	 * Collections, Universes).
	 *
	 * Call this at the top of enqueue() in each entity tab subclass, before
	 * any tab-specific assets.
	 *
	 * Extracts: DUP-004 (Session 136).
	 *
	 * @return void
	 * @since 1.1.0
	 */
	protected function enqueue_entity_base_assets(): void {
		wp_enqueue_style(
			'mbs-admin-settings-entities',
			plugins_url( 'assets/css/mbs-admin-settings-entities.css', MBS_DIR . 'bookshowroom.php' ),
			array( 'mbs-admin-settings' ),
			MBS_VERSION
		);
		foreach ( array( 'card', 'book-order', 'reading-order' ) as $component ) {
			wp_enqueue_style(
				'mbs-entities-' . $component,
				plugins_url( 'assets/css/admin/entities/entities-' . $component . '.css', MBS_DIR . 'bookshowroom.php' ),
				array( 'mbs-admin-settings-entities' ),
				MBS_VERSION
			);
		}
	}

	/**
	 * Handle entity saves submitted from an entity tab (Series, Collections,
	 * Universes). Subclasses supply the nonce/action/field names via the four
	 * protected accessor methods below.
	 *
	 * Returns early when the nonce field is absent (i.e. this tab's form was
	 * not submitted). On success, delegates to save() and issues a PRG
	 * redirect including a 'saved' query arg so render() can auto-expand the
	 * just-edited card.
	 *
	 * Hooked to admin_init by the subclass constructor.
	 *
	 * Extracts: DUP-005 (Session 136).
	 *
	 * @return void
	 * @since 1.1.0
	 */
	public function handle_save(): void {
		$nonce_field = $this->save_nonce_field();
		if ( ! isset( $_POST[ $nonce_field ] ) ) {
			return;
		}

		check_admin_referer( $this->save_nonce_action(), $nonce_field );

		if ( ! current_user_can( 'manage_options' ) ) {
			return;
		}

		$post_id_field = $this->save_post_id_field();
		if ( ! isset( $_POST[ $post_id_field ] ) ) {
			return;
		}

		$term_id = absint( $_POST[ $post_id_field ] );
		$this->save( $term_id );

		wp_safe_redirect( add_query_arg( array(
			'page'    => 'my-book-showroom-settings',
			'tab'     => $this->save_tab_slug(),
			'updated' => '1',
			'saved'   => $term_id,
		), admin_url( 'admin.php' ) ) );
		exit;
	}

	/**
	 * Save extended entity fields for a single term.
	 *
	 * Override in each entity tab subclass to persist term_meta fields.
	 * Called by handle_save() after security checks pass.
	 *
	 * @param int $term_id Validated, absint-cast term ID.
	 * @return void
	 */
	public function save( int $term_id ): void {}

	/**
	 * Save the mbs_term_layout term_meta for the given term.
	 *
	 * Reads mbs_term_layout from the POST payload, validates the submitted
	 * slug against the tier-appropriate layout allowlist (FREE: free slugs;
	 * PRO/PUB: all slugs), and persists via update_term_meta(). Empty string
	 * means "use the global default". Called by save() in each entity tab.
	 *
	 * Extracts: TAB-DUP-001 (Session 161).
	 *
	 * @param int $term_id Validated, absint-cast term ID.
	 * @return void
	 * @since 1.1.0
	 */
	protected function save_layout_field( int $term_id ): void {
		$allowed_layouts = ( mbs_is_pro() || mbs_is_pub() )
			? MBS_Layout_Registry::get_all_layouts()
			: MBS_Layout_Registry::get_free_layout_slugs();
		$allowed_layouts = array_merge( array( '' ), $allowed_layouts );
		$layout_raw      = isset( $_POST['mbs_term_layout'][ $term_id ] )
			? sanitize_key( wp_unslash( $_POST['mbs_term_layout'][ $term_id ] ) )
			: '';
		if ( ! in_array( $layout_raw, $allowed_layouts, true ) ) {
			$layout_raw = '';
		}
		update_term_meta( $term_id, 'mbs_term_layout', $layout_raw );
	}


	/**
	 * Save a banner attachment ID as term_meta for a taxonomy term.
	 *
	 * Reads the submitted attachment ID from $_POST[$meta_key][$term_id],
	 * validates via absint(), then either updates or deletes the $meta_key
	 * entry in term_meta. The tier gate (PRO/PUB check) must be applied by
	 * the caller before invoking this method.
	 *
	 * $post_key and $meta_key are the same string in all current entity tabs
	 * (e.g. 'mbs_series_banner_id'). Both parameters are kept separate so a
	 * future caller can map a differently-named POST field to an existing
	 * meta key without changing this method.
	 *
	 * Extracts: DUP-006 (Session 214).
	 *
	 * @param int    $term_id  Validated, absint-cast term ID.
	 * @param string $post_key POST array key, e.g. 'mbs_series_banner_id'.
	 * @param string $meta_key term_meta key to write, e.g. 'mbs_series_banner_id'.
	 * @return void
	 * @since 1.1.0
	 */
	protected function save_banner_field( int $term_id, string $post_key, string $meta_key ): void {
		$banner_id = isset( $_POST[ $post_key ][ $term_id ] )
			? absint( wp_unslash( $_POST[ $post_key ][ $term_id ] ) )
			: 0;
		if ( $banner_id ) {
			update_term_meta( $term_id, $meta_key, $banner_id );
		} else {
			delete_term_meta( $term_id, $meta_key );
		}
	}


	/**
	 * Localize the sort-preference data for the mbs-admin-sort-pref script.
	 *
	 * Reads the user's stored sort preference for this tab, validates it against
	 * the supplied allowlist, and passes ajaxUrl, nonce, tab slug, and current
	 * value to the registered JS handle. The script must already be enqueued
	 * before this method is called.
	 *
	 * Extracts: TAB-DUP-002 (Session 161).
	 *
	 * @param string   $tab          Tab slug, e.g. 'authors'. Also used as the
	 *                               suffix of the mbs_sort_pref_{$tab} user-meta key.
	 * @param string[] $valid_values Allowed sort-preference values for this tab.
	 * @param string   $default      Fallback value when stored pref is invalid.
	 * @return void
	 * @since 1.1.0
	 */
	protected function localize_sort_pref( string $tab, array $valid_values, string $default ): void {
		$sort_pref = (string) get_user_meta( get_current_user_id(), 'mbs_sort_pref_' . $tab, true );
		if ( ! in_array( $sort_pref, $valid_values, true ) ) {
			$sort_pref = $default;
		}
		wp_localize_script( 'mbs-admin-sort-pref', 'MBS_SortPref', array(
			'ajaxUrl' => admin_url( 'admin-ajax.php' ),
			'nonce'   => wp_create_nonce( 'mbs_sort_pref_nonce' ),
			'tab'     => $tab,
			'current' => $sort_pref,
		) );
	}

	/**
	 * The POST field name holding the nonce token.
	 *
	 * Override in each entity tab subclass. Example: 'mbs_series_nonce'.
	 *
	 * @return string
	 */
	protected function save_nonce_field(): string { return ''; }

	/**
	 * The nonce action string registered via wp_nonce_field().
	 *
	 * Override in each entity tab subclass. Example: 'mbs_series_save'.
	 *
	 * @return string
	 */
	protected function save_nonce_action(): string { return ''; }

	/**
	 * The tab slug used in the PRG redirect after a successful save.
	 *
	 * Override in each entity tab subclass. Example: 'series'.
	 *
	 * @return string
	 */
	protected function save_tab_slug(): string { return ''; }

	/**
	 * The POST field name holding the term ID being saved.
	 *
	 * Override in each entity tab subclass. Example: 'mbs_save_series_id'.
	 *
	 * @return string
	 */
	protected function save_post_id_field(): string { return ''; }
}
