<?php
/**
 * File Path: includes/migrations/class-mbs-migration-4-16.php
 *
 * Migration 4.16 — Retire mbs_book_meta postmeta orphans
 *
 * Migration 4.9 backfilled five scalar postmeta keys into mbs_book_meta but
 * did not delete the source rows. This migration removes those orphaned rows
 * from wp_postmeta for every book whose post_id already exists in
 * mbs_book_meta (i.e. has been migrated).
 *
 * Affected keys: mbs_isbn13, mbs_lifecycle_state, mbs_featured,
 *                mbs_coming_soon, mbs_word_count.
 *
 * Idempotent: a JOIN DELETE against rows that are already gone is a no-op.
 *
 * Finding: MIGS-001 (Session 161 Tier 3 test).
 *
 * @package MyBookShowroom
 * @since   1.7.0
 */

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

/**
 * Migration 4.16: delete orphaned postmeta rows retired by mbs_book_meta.
 */
// 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_16 implements MBS_Migration { // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedClassFound -- mbs_/MBS_ prefix is correct; scanner not configured with plugin prefix

	/**
	 * @return string Migration version string.
	 */
	public function version(): string {
		return '4.16';
	}

	/**
	 * Run the migration.
	 *
	 * Deletes wp_postmeta rows for the five keys that were migrated to
	 * mbs_book_meta in migration 4.9. Only rows whose post_id exists in
	 * mbs_book_meta are removed — books that were never migrated (if any)
	 * are left untouched.
	 *
	 * @return void
	 */
	public function up(): void {
		global $wpdb;

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

		// Confirm the target table exists before attempting the DELETE.
		// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
		$exists = $wpdb->get_var( "SHOW TABLES LIKE '{$book_meta_table}'" ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- DAO layer -- direct DB access is intentional per MBS architecture
		if ( $exists !== $book_meta_table ) {
			return;
		}

		$keys         = array(
			'mbs_isbn13',
			'mbs_lifecycle_state',
			'mbs_featured',
			'mbs_coming_soon',
			'mbs_word_count',
		);
		$placeholders = implode( ', ', array_fill( 0, count( $keys ), '%s' ) );

		// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
		$wpdb->query( // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- DAO layer -- direct DB access is intentional per MBS architecture
			$wpdb->prepare(
				"DELETE pm
				 FROM {$wpdb->postmeta} pm // phpcs:ignore WordPress.DB.PreparedSQLPlaceholders.ReplacementsWrongNumber, WordPress.DB.PreparedSQLPlaceholders.UnfinishedPrepare -- safe: variadic ...$params / ...$row_ids correctly matches prepared placeholders; PHPCS cannot statically verify spread arg count
				 INNER JOIN {$book_meta_table} bm ON bm.post_id = pm.post_id
				 WHERE pm.meta_key IN ({$placeholders})", // phpcs:ignore WordPress.DB.PreparedSQLPlaceholders.ReplacementsWrongNumber, WordPress.DB.PreparedSQLPlaceholders.UnfinishedPrepare -- safe: variadic ...$params spread correctly matches prepared placeholders; PHPCS cannot statically verify spread arg count
				...$keys
			)
		);
	}
}
