<?php
/**
 * File Path: includes/migrations/class-mbs-migration-4-34.php
 *
 * Migration 4.34 -- Sprint Reading Order Display:
 * Add series_reading_order_position, series_reading_order_hidden,
 * universe_reading_order_hidden columns to mbs_book_meta.
 * Migrate mbs_series_reading_order_position postmeta to mbs_book_meta.
 * Migrate mbs_series_default_sort term_meta to mbs_series_archive_display_mode.
 *
 * All three sub-steps are idempotent.
 *
 * @package MyBookShowroom
 * @since   4.34.0
 */

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

// phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- DAO/migration: table names via $wpdb->prefix are trusted server-controlled values, not user input
// phpcs:disable PluginCheck.Security.DirectDB.UnescapedDBParameter -- same reason: $wpdb->prefix table names are safe; $where_sql is built from sanitized server-controlled inputs
class MBS_Migration_4_34 implements MBS_Migration { // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedClassFound -- mbs_/MBS_ prefix is correct; scanner not configured with plugin prefix

	/**
	 * Returns the migration version string.
	 *
	 * @return string
	 */
	public function version(): string { return '4.34'; }

	/**
	 * Run the migration.
	 *
	 * Sub-step A: Add columns to mbs_book_meta (idempotent via SHOW COLUMNS guard).
	 * Sub-step B: Migrate mbs_series_reading_order_position postmeta to mbs_book_meta.
	 * Sub-step C: Migrate mbs_series_default_sort term_meta to mbs_series_archive_display_mode.
	 *
	 * @return void
	 */
	public function up(): void {
		global $wpdb;

		$bm_table = $wpdb->prefix . 'mbs_book_meta';

		// ---- Sub-step A: Add columns (idempotent via SHOW COLUMNS guard) ----
		// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching
		$col_exists = $wpdb->get_var( "SHOW COLUMNS FROM `{$bm_table}` LIKE 'series_reading_order_position'" );
		if ( ! $col_exists ) {
			// phpcs:disable WordPress.DB.DirectDatabaseQuery.SchemaChange
			$wpdb->query( // phpcs:ignore WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.DirectDatabaseQuery.SchemaChange, WordPress.DB.DirectDatabaseQuery.DirectQuery -- DAO layer -- direct DB access is intentional per MBS architecture
				"ALTER TABLE `{$bm_table}`
				  ADD COLUMN series_reading_order_position DECIMAL(10,2) NULL AFTER series_position,
				  ADD COLUMN series_reading_order_hidden TINYINT(1) NOT NULL DEFAULT 0
				    AFTER series_reading_order_position,
				  ADD COLUMN universe_reading_order_hidden TINYINT(1) NOT NULL DEFAULT 0
				    AFTER series_reading_order_hidden"
			);
			// phpcs:enable WordPress.DB.DirectDatabaseQuery.SchemaChange
			// Add index separately (idempotent guard not needed; fails silently if dup).
			// phpcs:disable WordPress.DB.DirectDatabaseQuery.SchemaChange
			$wpdb->query( "ALTER TABLE `{$bm_table}` ADD KEY idx_series_ro_pos (series_reading_order_position)" ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.SchemaChange -- DAO layer -- direct DB access is intentional per MBS architecture
			// phpcs:enable WordPress.DB.DirectDatabaseQuery.SchemaChange
		}

		// ---- Sub-step B: Migrate postmeta to mbs_book_meta ----
		// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching
		$rows = $wpdb->get_results(
			"SELECT post_id, meta_value FROM {$wpdb->postmeta}
			 WHERE meta_key = 'mbs_series_reading_order_position' AND meta_value != ''"
		);
		if ( $rows ) {
			foreach ( $rows as $row ) {
				$pos = max( 0.0, (float) $row->meta_value );
				// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery
				$wpdb->query( $wpdb->prepare( // phpcs:ignore WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.DirectDatabaseQuery.DirectQuery -- DAO layer -- direct DB access is intentional per MBS architecture
					"INSERT INTO `{$bm_table}` (post_id, series_reading_order_position)
					 VALUES (%d, %f)
					 ON DUPLICATE KEY UPDATE
					   series_reading_order_position = COALESCE(series_reading_order_position, VALUES(series_reading_order_position))",
					$row->post_id, $pos
				) );
				// V5-D-016: Delete postmeta only for this row, only when the INSERT
				// succeeded. Bulk delete at end would silently drop data if any INSERT fails.
				if ( ! $wpdb->last_error ) {
					delete_post_meta( (int) $row->post_id, 'mbs_series_reading_order_position' );
				}
			}
			// Bulk delete removed — per-row delete above is now the only cleanup path.
		}

		// ---- Sub-step C: Migrate mbs_series_default_sort term_meta ----
		$terms = get_terms( array(
			'taxonomy'   => 'mbs_series',
			'hide_empty' => false,
			'meta_key'   => 'mbs_series_default_sort', // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_key -- standard WP query pattern; VIP rule does not apply to this plugin
			'fields'     => 'ids',
		) );
		if ( ! is_wp_error( $terms ) && ! empty( $terms ) ) {
			foreach ( $terms as $term_id ) {
				$val = get_term_meta( $term_id, 'mbs_series_default_sort', true );
				update_term_meta( $term_id, 'mbs_series_archive_display_mode', $val );
				delete_term_meta( $term_id, 'mbs_series_default_sort' );
			}
		}
	}
}
