<?php
/**
 * File Path: includes/class-mbs-sort-hooks.php
 *
 * Sort Hook Registrar — F-SORT-1
 *
 * Wires mbs_compute_sort_key() into the WordPress save pipeline so that
 * mbs_sort_key (term_meta) and mbs_title_sort_key (postmeta) are always
 * current without any manual intervention.
 *
 * Registered via MBS_Sort_Hooks::init() at init priority 20, after all
 * taxonomies are registered at priority 10.
 *
 * Covered taxonomies (title-like, not person names):
 *   mbs_series, mbs_collection, mbs_universe, mbs_genre, mbs_theme,
 *   mbs_content_warning, mbs_publisher, mbs_imprint
 *
 * Person-name sort (mbs_name_key on mbs_author / mbs_contributor) is
 * handled by MBS_Taxonomy_Author and MBS_Taxonomy_Contributor — not here.
 *
 * @package    My_Book_Showroom
 * @subpackage Includes
 * @since      1.1.0
 */

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

class MBS_Sort_Hooks {

	/**
	 * Register the F-SORT-1 init hook.
	 * Moved from MBS_Plugin::load_post_types_and_taxonomies() — REFACTOR-PLAN-ARCH-001 Batch B.
	 */
	public static function register_hooks(): void {
		add_action( 'init', array( __CLASS__, 'init' ), 20 );
	}

	/**
	 * Register all sort-key hooks.
	 *
	 * Called at init priority 20 (after taxonomy registration at priority 10).
	 */
	public static function init(): void {
		foreach ( self::title_taxonomies() as $tax ) {
			add_action( 'created_' . $tax, array( __CLASS__, 'save_term_sort_key' ) );
			add_action( 'edited_'  . $tax, array( __CLASS__, 'save_term_sort_key' ) );
		}

		// Priority 20 — runs after all metabox save handlers (priority 10).
		add_action( 'save_post_' . MBS_POST_TYPE, array( __CLASS__, 'save_post_sort_key' ), 20, 2 );

		// Sprint 163-C: keep mbs_book_meta.series_position in sync whenever the
		// mbs_series_position postmeta key is written anywhere in the codebase.
		// Uses WordPress postmeta hooks (updated/added/deleted) so no write path
		// needs to be individually patched.
		add_action( 'updated_post_meta', array( __CLASS__, 'sync_series_position' ), 10, 4 );
		add_action( 'added_post_meta',   array( __CLASS__, 'sync_series_position' ), 10, 4 );
		add_action( 'deleted_post_meta', array( __CLASS__, 'clear_series_position' ), 10, 4 );
	}

	/**
	 * Taxonomies that store a title-like mbs_sort_key.
	 *
	 * Returned as a method (not a class constant) so that the MBS_TAX_* defines
	 * are resolved at call time — safe at any hook priority.
	 *
	 * @return string[]
	 */
	public static function title_taxonomies(): array {
		return array(
			MBS_TAX_SERIES,
			MBS_TAX_COLLECTION,
			MBS_TAX_UNIVERSE,
			MBS_TAX_GENRE,
			MBS_TAX_THEME,
			MBS_TAX_CONTENT_WARNING,
			MBS_TAX_PUBLISHER,
			MBS_TAX_IMPRINT,
		);
	}

	/**
	 * Compute and store mbs_sort_key for a taxonomy term.
	 *
	 * Hooked to created_{taxonomy} and edited_{taxonomy}.
	 *
	 * @param int $term_id Term ID.
	 */
	public static function save_term_sort_key( int $term_id ): void {
		$term = get_term( $term_id );
		if ( ! $term instanceof WP_Term ) {
			return;
		}
		update_term_meta( $term_id, 'mbs_sort_key', mbs_compute_sort_key( $term->name ) );
	}

	/**
	 * Compute and store mbs_title_sort_key for a book post.
	 *
	 * Hooked to save_post_mbs_book at priority 20.
	 * Skips revisions and autosaves.
	 *
	 * @param int     $post_id Post ID.
	 * @param WP_Post $post    Post object.
	 */
	public static function save_post_sort_key( int $post_id, WP_Post $post ): void {
		if ( wp_is_post_revision( $post_id ) || wp_is_post_autosave( $post_id ) ) {
			return;
		}
		$sort_key = mbs_compute_sort_key( $post->post_title );
		update_post_meta( $post_id, 'mbs_title_sort_key', $sort_key );
		// Sprint 163-C: dual-write to mbs_book_meta.title_sort_key so the
		// posts_clauses sort path reads a current value without postmeta JOIN.
		MBS_Book_Meta_DAO::upsert( $post_id, 'mbs_title_sort_key', $sort_key );
	}

	/**
	 * Sync mbs_book_meta.series_position when mbs_series_position postmeta is added or updated.
	 *
	 * Hooked to updated_post_meta and added_post_meta at priority 10.
	 * Fires for every postmeta write; the meta_key guard limits work to
	 * mbs_series_position only. Sprint 163-C.
	 *
	 * @param int    $meta_id    Meta row ID (unused).
	 * @param int    $post_id    Book post ID.
	 * @param string $meta_key   Postmeta key name.
	 * @param mixed  $meta_value New value.
	 * @return void
	 */
	public static function sync_series_position( int $meta_id, int $post_id, string $meta_key, $meta_value ): void {
		if ( 'mbs_series_position' !== $meta_key ) {
			return;
		}
		MBS_Book_Meta_DAO::upsert( $post_id, 'mbs_series_position', $meta_value );
	}

	/**
	 * Clear mbs_book_meta.series_position when mbs_series_position postmeta is deleted.
	 *
	 * Hooked to deleted_post_meta at priority 10. Sprint 163-C.
	 *
	 * @param int[]  $meta_ids   Meta row IDs (unused).
	 * @param int    $post_id    Book post ID.
	 * @param string $meta_key   Postmeta key name.
	 * @param mixed  $meta_value Deleted value (unused).
	 * @return void
	 */
	public static function clear_series_position( array $meta_ids, int $post_id, string $meta_key, $meta_value ): void {
		if ( 'mbs_series_position' !== $meta_key ) {
			return;
		}
		MBS_Book_Meta_DAO::upsert( $post_id, 'mbs_series_position', '' );
	}
}
