<?php
/**
 * File Path: includes/shortcodes/class-mbs-shortcode-taxonomy-base.php
 *
 * Shortcode Taxonomy Base
 *
 * Abstract base class for the four taxonomy shortcodes:
 *   - MBS_Shortcode_Series   (mbs_series)
 *   - MBS_Shortcode_Genre    (mbs_genre)
 *   - MBS_Shortcode_Collection (mbs_collection)
 *   - MBS_Shortcode_Universe (mbs_universe)
 *
 * Consolidates the shared scaffold that existed in all four files (D-010,
 * Session 117): license gate, shortcode_atts normalization, slug validation,
 * WP_Term lookup, ob_start / ob_get_clean wrapper, and the outer div/header
 * HTML skeleton.
 *
 * Subclass contract:
 *   - get_taxonomy()       Return the taxonomy slug (e.g. MBS_TAX_SERIES).
 *   - get_shortcode_tag()  Return the shortcode tag (e.g. "mbs_series").
 *   - render_body()        Emit the content area after the header.
 *
 * Optional hook overrides:
 *   - render_extra_header() Inserted inside <header>, after <h2>, before
 *                           the description block (Collection overrides).
 *   - render_after_header() Inserted immediately after </header>, before
 *                           render_body() (Series overrides for PRO banner).
 *
 * Registration: each concrete subclass registers its own shortcode tag via
 * add_shortcode() in its own init() or via MBS_Plugin; this base class does
 * not register anything.
 *
 * @package    My_Book_Showroom
 * @subpackage Includes/Shortcodes
 * @since      1.0.0
 */

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

abstract class MBS_Shortcode_Taxonomy_Base { // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedClassFound -- mbs_/MBS_ prefix is correct; scanner not configured with plugin prefix

	// -------------------------------------------------------------------------
	// Subclass contract
	// -------------------------------------------------------------------------

	/**
	 * Return the taxonomy slug for this shortcode.
	 *
	 * @return string e.g. MBS_TAX_SERIES, MBS_TAX_GENRE
	 */
	abstract protected static function get_taxonomy(): string;

	/**
	 * Return the shortcode tag string for this shortcode.
	 *
	 * @return string e.g. "mbs_series", "mbs_genre"
	 */
	abstract protected static function get_shortcode_tag(): string;

	/**
	 * Emit the content area (book grid / list) for this taxonomy term.
	 *
	 * Called inside the ob_start() buffer, after the closing </header> and
	 * render_after_header().
	 *
	 * @param WP_Term $term     The resolved taxonomy term.
	 * @param int     $per_page Books per page from shortcode atts.
	 * @return void
	 */
	abstract protected static function render_body( WP_Term $term, int $per_page ): void;

	// -------------------------------------------------------------------------
	// Optional hook overrides
	// -------------------------------------------------------------------------

	/**
	 * Optional: emit extra markup inside <header>, after <h2>, before description.
	 *
	 * Default is a no-op. Collection overrides to output the collection-type label.
	 *
	 * @param WP_Term $term The resolved taxonomy term.
	 * @return void
	 */
	protected static function render_extra_header( WP_Term $term ): void {}

	/**
	 * Optional: emit markup after </header> and before render_body().
	 *
	 * Default is a no-op. Series overrides to output the F-011 PRO reading-order
	 * upgrade banner.
	 *
	 * @param WP_Term $term The resolved taxonomy term.
	 * @return void
	 */
	protected static function render_after_header( WP_Term $term ): void {}

	// -------------------------------------------------------------------------
	// Concrete scaffold
	// -------------------------------------------------------------------------

	/**
	 * WordPress shortcode callback.
	 *
	 * Registered via add_shortcode() in each concrete subclass. Late static
	 * binding ensures static:: resolves to the concrete class at call time,
	 * so WordPress can call MBS_Shortcode_Series::render() and have
	 * static::get_taxonomy() resolve to MBS_TAX_SERIES.
	 *
	 * Shared scaffold:
	 *   1. License gate (mbs_is_registered()).
	 *   2. Normalize shortcode_atts (slug, per_page).
	 *   3. Validate slug attribute.
	 *   4. Look up WP_Term.
	 *   5. Build outer wrapper + header HTML via ob_start().
	 *   6. Delegate body to static::render_body().
	 *
	 * @param array $atts Raw shortcode attributes from WordPress.
	 * @return string Rendered HTML or an HTML comment on error.
	 */
	public static function render( array $atts = array() ): string {
		if ( ! mbs_is_registered() ) {
			return '';
		}

		$tag  = static::get_shortcode_tag();
		$atts = shortcode_atts(
			array(
				'slug'     => '',
				'per_page' => '20',
			),
			$atts,
			$tag
		);

		$slug     = sanitize_title( $atts['slug'] );
		$per_page = (int) $atts['per_page'];

		if ( '' === $slug ) {
			return '<!-- MBS: ' . esc_html( $tag ) . ': slug attribute required -->';
		}

		$term = get_term_by( 'slug', $slug, static::get_taxonomy() );

		if ( ! $term instanceof WP_Term ) {
			return '<!-- MBS: ' . esc_html( $tag ) . ': term not found -->';
		}

		// Derive the entity slug for CSS class: mbs_series -> series.
		$entity_slug = str_replace( 'mbs_', '', $tag );

		ob_start();
		?>
		<div class="mbs-wrapper mbs-shortcode mbs-shortcode-<?php echo esc_attr( $entity_slug ); ?>">
			<header class="mbs-shortcode-header">
				<h2 class="mbs-shortcode-title"><?php echo esc_html( $term->name ); ?></h2>
				<?php static::render_extra_header( $term ); ?>
				<?php if ( ! empty( $term->description ) ) : ?>
					<div class="mbs-term-description"><?php echo wp_kses_post( wpautop( $term->description ) ); ?></div>
				<?php endif; ?>
			</header>
			<?php static::render_after_header( $term ); ?>
			<?php static::render_body( $term, $per_page ); ?>
		</div>
		<?php
		return ob_get_clean();
	}
}
