<?php
/**
 * Ability: wp-agent/set-seo-meta
 *
 * Detects Rank Math, Yoast, AIOSEO or SEOPress, writes the SEO fields through
 * the plugin's native path, reads them back, and reports per-field persistence.
 * This is the defense against SEO meta being silently dropped.
 *
 * @package WPAgentCompanion
 */

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

function wpagent_ability_set_seo_meta(): array {
	return array(
		'label'               => __( 'SEO-Meta setzen und verifizieren', 'wp-agent-companion' ),
		'description'         => __( 'Schreibt und verifiziert Titel, Description, Fokus-Keyword, Canonical, Robots-Overrides, Open-Graph- und X/Twitter-Texte sowie Cornerstone-Inhalte über unterstützte Provider und rollt Teilfehler zurück.', 'wp-agent-companion' ),
		'category'            => 'wp-agent',
		'input_schema'        => array(
			'type'                 => 'object',
			'properties'           => array(
				'post_id'                => array( 'type' => 'integer' ),
				'seo_title'              => array( 'type' => 'string' ),
				'seo_description'        => array( 'type' => 'string' ),
				'focus_keyword'          => array( 'type' => 'string' ),
				'canonical_url'          => array( 'type' => 'string' ),
				'robots_noindex'         => array( 'type' => 'boolean' ),
				'robots_nofollow'        => array( 'type' => 'boolean' ),
				'open_graph_title'       => array( 'type' => 'string' ),
				'open_graph_description' => array( 'type' => 'string' ),
				'twitter_title'          => array( 'type' => 'string' ),
				'twitter_description'    => array( 'type' => 'string' ),
				'cornerstone'            => array( 'type' => 'boolean' ),
			),
			'required'             => array( 'post_id' ),
			'additionalProperties' => false,
		),
		'output_schema'       => array(
			'type'       => 'object',
			'properties' => array(
				'post_id'            => array( 'type' => 'integer' ),
				'plugin'             => array( 'type' => 'string' ),
				'written'            => array( 'type' => 'object' ),
				'verified'           => array( 'type' => 'boolean' ),
				'indexable_verified' => array( 'type' => array( 'boolean', 'null' ) ),
				'rolled_back'        => array( 'type' => 'boolean' ),
				'rollback_verified'  => array( 'type' => array( 'boolean', 'null' ) ),
			),
		),
		'execute_callback'    => 'wpagent_exec_set_seo_meta',
		'permission_callback' => 'wpagent_perm_edit_posts',
		'meta'                => array(
			'show_in_rest' => true,
			'annotations'  => array(
				'readonly'    => false,
				'destructive' => false,
				'idempotent'  => true,
			),
		),
	);
}

/**
 * @param array<string,string|bool|null> $fields
 * @return array|WP_Error
 */
function wpagent_set_aioseo_meta( int $post_id, array $fields ) {
	$update = wpagent_companion_get_registered_ability( 'aioseo-posts/seo-data-update' );
	if ( ! $update instanceof WP_Ability ) {
		return new WP_Error( 'wpagent_aioseo_api_missing', __( 'AIOSEO stellt seine native SEO-Ability nicht bereit.', 'wp-agent-companion' ), array( 'status' => 409 ) );
	}
	$input = array( 'postId' => $post_id );
	$map   = array( 'title' => 'title', 'description' => 'description', 'focus_keyword' => 'focus_keyphrase' );
	foreach ( $map as $field => $target ) {
		if ( null !== $fields[ $field ] ) {
			$input[ $target ] = $fields[ $field ];
		}
	}
	if ( null !== $fields['canonical_url'] ) {
		$input['canonical_url'] = $fields['canonical_url'];
	}
	if ( null !== $fields['robots_noindex'] || null !== $fields['robots_nofollow'] ) {
		$input['robots'] = array( 'use_default' => false );
		if ( null !== $fields['robots_noindex'] ) {
			$input['robots']['noindex'] = $fields['robots_noindex'];
		}
		if ( null !== $fields['robots_nofollow'] ) {
			$input['robots']['nofollow'] = $fields['robots_nofollow'];
		}
	}
	$social_map = array(
		'open_graph_title'        => 'og_title',
		'open_graph_description'  => 'og_description',
		'twitter_title'           => 'twitter_title',
		'twitter_description'     => 'twitter_description',
	);
	foreach ( $social_map as $field => $target ) {
		if ( null !== $fields[ $field ] ) {
			$input['social'][ $target ] = $fields[ $field ];
		}
	}
	if ( 1 === count( $input ) ) {
		return new WP_Error( 'wpagent_no_fields', __( 'Kein SEO-Feld übergeben.', 'wp-agent-companion' ), array( 'status' => 400 ) );
	}
	if ( true !== $update->check_permissions( $input ) ) {
		return new WP_Error( 'wpagent_forbidden', __( 'AIOSEO erlaubt diesem Benutzer keine Änderungen an den SEO-Daten.', 'wp-agent-companion' ), array( 'status' => 403 ) );
	}

	$mutation = null;
	$before   = wpagent_read_aioseo_meta( $post_id );
	if ( is_wp_error( $before ) && 'not_found' === $before->get_error_code() ) {
		$mutation = wpagent_initialize_aioseo_post_record( $post_id );
		if ( is_wp_error( $mutation ) ) {
			return $mutation;
		}
	}
	if ( is_wp_error( $before ) ) {
		if ( 'not_found' !== $before->get_error_code() ) {
			return $before;
		}
	}

	if ( null === $mutation ) {
		$mutation = wpagent_aioseo_begin_mutation( $post_id );
		if ( is_wp_error( $mutation ) ) {
			return $mutation;
		}
	}
	$result = $update->execute( $input );
	if ( is_wp_error( $result ) ) {
		return wpagent_aioseo_error_with_transaction_rollback( $result, $post_id, $mutation, 400 );
	}
	$fresh = wpagent_read_seo_meta( $post_id, 'aioseo' );
	if ( is_wp_error( $fresh ) ) {
		return wpagent_aioseo_error_with_transaction_rollback( $fresh, $post_id, $mutation, 500 );
	}
	$verification = wpagent_verify_seo_snapshot( $fields, $fresh );
	$mirrors      = wpagent_aioseo_verify_touched_mirrors( $post_id, $fields );
	if ( is_wp_error( $mirrors ) ) {
		return wpagent_aioseo_error_with_transaction_rollback( $mirrors, $post_id, $mutation, 500 );
	}
	foreach ( $mirrors as $field => $persisted ) {
		if ( isset( $verification['written'][ $field ] ) ) {
			$verification['written'][ $field ]['persisted'] = $verification['written'][ $field ]['persisted'] && $persisted;
		}
		$verification['verified'] = $verification['verified'] && $persisted;
	}
	if ( ! $verification['verified'] ) {
		$rollback_verified = wpagent_aioseo_rollback_mutation( $post_id, $mutation );
		return array(
			'post_id'            => $post_id,
			'plugin'             => 'aioseo',
			'written'            => $verification['written'],
			'verified'           => false,
			'indexable_verified' => null,
			'rolled_back'        => true,
			'rollback_verified'  => $rollback_verified,
		);
	}
	if ( ! wpagent_aioseo_commit_mutation( $post_id ) ) {
		return new WP_Error( 'wpagent_aioseo_commit_failed', __( 'Die verifizierten AIOSEO-Daten konnten nicht sicher festgeschrieben werden.', 'wp-agent-companion' ), array( 'status' => 500, 'rolled_back' => true, 'rollback_verified' => false ) );
	}
	return array(
		'post_id'            => $post_id,
		'plugin'             => 'aioseo',
		'written'            => $verification['written'],
		'verified'           => $verification['verified'],
		'indexable_verified' => null,
		'rolled_back'        => false,
		'rollback_verified'  => null,
	);
}

/**
 * AIOSEO spiegelt einige Modellfelder für Übersetzungs-Plugins zusätzlich in
 * WordPress-Postmeta. Ein exakter Rollback muss deren Existenz und alle Werte
 * unabhängig vom eigentlichen AIOSEO-Datensatz wiederherstellen.
 *
 * @return string[]
 */
function wpagent_aioseo_mirrored_meta_keys(): array {
	return array(
		'_aioseo_title',
		'_aioseo_description',
		'_aioseo_og_title',
		'_aioseo_og_description',
		'_aioseo_og_article_section',
		'_aioseo_twitter_title',
		'_aioseo_twitter_description',
		'_aioseo_keywords',
		'_aioseo_og_article_tags',
	);
}

/** @return array<string,mixed>|WP_Error */
function wpagent_initialize_aioseo_post_record( int $post_id ) {
	global $wpdb;
	$supported = wpagent_aioseo_require_transactional_storage();
	if ( is_wp_error( $supported ) ) {
		return $supported;
	}
	$model = 'AIOSEO\\Plugin\\Common\\Models\\Post';
	if ( ! is_callable( array( $model, 'savePost' ) ) ) {
		return new WP_Error( 'wpagent_aioseo_api_missing', __( 'AIOSEO stellt sein natives Beitragsmodell nicht bereit.', 'wp-agent-companion' ), array( 'status' => 409 ) );
	}
	$idle = wpagent_aioseo_require_idle_transaction( 'SERIALIZABLE' );
	if ( is_wp_error( $idle ) ) {
		return $idle;
	}
	if ( false === $wpdb->query( 'START TRANSACTION' ) ) {
		return new WP_Error( 'wpagent_aioseo_transaction_failed', __( 'Die sichere AIOSEO-Initialisierung konnte nicht gestartet werden.', 'wp-agent-companion' ), array( 'status' => 500 ) );
	}

	$rows = wpagent_aioseo_storage_rows( $post_id, true );
	if ( is_wp_error( $rows ) ) {
		$wpdb->query( 'ROLLBACK' );
		return $rows;
	}
	if ( count( $rows ) > 1 ) {
		$wpdb->query( 'ROLLBACK' );
		return new WP_Error( 'wpagent_aioseo_ambiguous_record', __( 'AIOSEO besitzt für diesen Beitrag mehrere SEO-Datensätze. Die Änderung wurde abgebrochen.', 'wp-agent-companion' ), array( 'status' => 409, 'record_count' => count( $rows ) ) );
	}
	$row_before = 1 === count( $rows ) ? $rows[0] : null;

	$postmeta = wpagent_aioseo_quote_identifier( $wpdb->postmeta );
	$locked   = $wpdb->get_col( $wpdb->prepare( "SELECT meta_id FROM {$postmeta} WHERE post_id = %d FOR UPDATE", $post_id ) ); // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
	if ( ! is_array( $locked ) ) {
		$wpdb->query( 'ROLLBACK' );
		return new WP_Error( 'wpagent_aioseo_transaction_failed', __( 'Die gespiegelten AIOSEO-Metadaten konnten nicht sicher gesperrt werden.', 'wp-agent-companion' ), array( 'status' => 500 ) );
	}
	$mirrors_before = wpagent_capture_seo_postmeta( $post_id, wpagent_aioseo_mirrored_meta_keys() );
	if ( null !== $row_before ) {
		wpagent_aioseo_reset_read_caches( $post_id );
		$before = wpagent_read_aioseo_meta( $post_id );
		if ( is_wp_error( $before ) ) {
			$wpdb->query( 'ROLLBACK' );
			return $before;
		}
		return array(
			'row'     => $row_before,
			'mirrors' => $mirrors_before,
			'before'  => $before,
		);
	}

	try {
		$result = $model::savePost( $post_id, array( 'canonicalUrl' => null ) );
	} catch ( Throwable $throwable ) {
		return wpagent_aioseo_initialization_error_with_rollback(
			new WP_Error( 'wpagent_aioseo_initialize_failed', __( 'AIOSEO konnte den SEO-Datensatz für diesen Beitrag nicht anlegen.', 'wp-agent-companion' ), array( 'status' => 500, 'provider_error' => $throwable->getMessage() ) ),
			$post_id,
			$mirrors_before
		);
	}
	if ( is_string( $result ) && '' !== $result ) {
		return wpagent_aioseo_initialization_error_with_rollback(
			new WP_Error( 'wpagent_aioseo_initialize_failed', __( 'AIOSEO konnte den SEO-Datensatz für diesen Beitrag nicht anlegen.', 'wp-agent-companion' ), array( 'status' => 500, 'provider_error' => $result ) ),
			$post_id,
			$mirrors_before
		);
	}

	$created = wpagent_aioseo_storage_rows( $post_id, true );
	if ( is_wp_error( $created ) || 1 !== count( $created ) ) {
		return wpagent_aioseo_initialization_error_with_rollback(
			new WP_Error( 'wpagent_aioseo_initialize_failed', __( 'AIOSEO konnte den neuen SEO-Datensatz nicht eindeutig bestätigen.', 'wp-agent-companion' ), array( 'status' => 500, 'record_count' => is_array( $created ) ? count( $created ) : null ) ),
			$post_id,
			$mirrors_before
		);
	}
	wpagent_aioseo_reset_read_caches( $post_id );
	return array(
		'row'     => null,
		'mirrors' => $mirrors_before,
		'before'  => null,
	);
}

/**
 * @param array<string,array<int,mixed>> $mirrors_before
 */
function wpagent_aioseo_initialization_error_with_rollback( WP_Error $error, int $post_id, array $mirrors_before ): WP_Error {
	global $wpdb;
	$rolled_back = false !== $wpdb->query( 'ROLLBACK' );
	wpagent_aioseo_reset_read_caches( $post_id );
	$rows = wpagent_aioseo_storage_rows( $post_id );
	$rollback_verified = $rolled_back
		&& is_array( $rows )
		&& 0 === count( $rows )
		&& $mirrors_before === wpagent_capture_seo_postmeta( $post_id, wpagent_aioseo_mirrored_meta_keys() );
	$data = $error->get_error_data();
	$data = is_array( $data ) ? $data : array();
	$error->add_data(
		array_merge(
			array( 'status' => 500 ),
			$data,
			array(
				'rolled_back'      => $rolled_back,
				'rollback_verified' => $rollback_verified,
			)
		)
	);
	return $error;
}

/**
 * @param array<string,mixed> $mutation
 */
function wpagent_aioseo_error_with_transaction_rollback( WP_Error $error, int $post_id, array $mutation, int $default_status ): WP_Error {
	$rollback_verified = wpagent_aioseo_rollback_mutation( $post_id, $mutation );
	$data              = $error->get_error_data();
	$data              = is_array( $data ) ? $data : array();
	$error->add_data(
		array_merge(
			array( 'status' => $default_status ),
			$data,
			array(
				'rolled_back'      => true,
				'rollback_verified' => $rollback_verified,
			)
		)
	);
	return $error;
}

/**
 * @param array<string,string|bool|null> $fields
 * @return array|WP_Error
 */
function wpagent_set_seopress_meta( int $post_id, array $fields ) {
	$meta_map = array(
		'title'                   => '_seopress_titles_title',
		'description'             => '_seopress_titles_desc',
		'focus_keyword'           => '_seopress_analysis_target_kw',
		'canonical_url'           => '_seopress_robots_canonical',
		'robots_noindex'          => '_seopress_robots_index',
		'robots_nofollow'         => '_seopress_robots_follow',
		'open_graph_title'        => '_seopress_social_fb_title',
		'open_graph_description'  => '_seopress_social_fb_desc',
		'twitter_title'           => '_seopress_social_twitter_title',
		'twitter_description'     => '_seopress_social_twitter_desc',
	);
	$touched_meta_keys = array();
	foreach ( $meta_map as $field => $meta_key ) {
		if ( null !== $fields[ $field ] ) {
			$touched_meta_keys[] = $meta_key;
		}
	}
	$before = wpagent_capture_seo_postmeta( $post_id, $touched_meta_keys );

	$global_fields = array(
		$fields['title'],
		$fields['description'],
		$fields['canonical_url'],
		$fields['robots_noindex'],
		$fields['robots_nofollow'],
		$fields['open_graph_title'],
		$fields['open_graph_description'],
		$fields['twitter_title'],
		$fields['twitter_description'],
	);
	if ( wpagent_seo_fields_have_value( $global_fields ) && function_exists( 'seopress_metabox_role_is_blocked' ) && seopress_metabox_role_is_blocked( 'GLOBAL' ) ) {
		return new WP_Error( 'wpagent_forbidden', __( 'SEOPress erlaubt dieser Rolle keine Änderungen an Titel oder Description.', 'wp-agent-companion' ), array( 'status' => 403 ) );
	}
	if ( null !== $fields['focus_keyword'] && function_exists( 'seopress_metabox_role_is_blocked' ) && seopress_metabox_role_is_blocked( 'CONTENT_ANALYSIS' ) ) {
		return new WP_Error( 'wpagent_forbidden', __( 'SEOPress erlaubt dieser Rolle keine Änderungen am Fokus-Keyword.', 'wp-agent-companion' ), array( 'status' => 403 ) );
	}
	if ( null !== $fields['title'] || null !== $fields['description'] ) {
		$path    = "/seopress/v1/posts/{$post_id}/title-description-metas";
		$request = new WP_REST_Request( 'PUT', $path );
		if ( null !== $fields['title'] ) {
			$request->set_param( 'title', $fields['title'] );
		}
		if ( null !== $fields['description'] ) {
			$request->set_param( 'description', $fields['description'] );
		}
		$response = rest_do_request( $request );
		if ( $response->is_error() ) {
			$error = new WP_Error( 'wpagent_seopress_write_failed', __( 'SEOPress hat Titel oder Description abgelehnt.', 'wp-agent-companion' ), array( 'status' => $response->get_status(), 'response' => $response->get_data() ) );
			return wpagent_seo_error_with_postmeta_rollback( $error, $post_id, $before );
		}
	}
	if ( null !== $fields['focus_keyword'] ) {
		$path    = "/seopress/v1/posts/{$post_id}/target-keywords";
		$request = new WP_REST_Request( 'PUT', $path );
		$request->set_param( '_seopress_analysis_target_kw', $fields['focus_keyword'] );
		$response = rest_do_request( $request );
		if ( $response->is_error() ) {
			$error = new WP_Error( 'wpagent_seopress_write_failed', __( 'SEOPress hat das Fokus-Keyword abgelehnt.', 'wp-agent-companion' ), array( 'status' => $response->get_status(), 'response' => $response->get_data() ) );
			return wpagent_seo_error_with_postmeta_rollback( $error, $post_id, $before );
		}
	}

	$robots_map = array(
		'canonical_url'   => '_seopress_robots_canonical',
		'robots_noindex'  => '_seopress_robots_index',
		'robots_nofollow' => '_seopress_robots_follow',
	);
	$robots_request = new WP_REST_Request( 'PUT', "/seopress/v1/posts/{$post_id}/meta-robot-settings" );
	$has_robots     = false;
	foreach ( $robots_map as $field => $target ) {
		if ( null === $fields[ $field ] ) {
			continue;
		}
		$value = str_starts_with( $field, 'robots_' ) ? ( $fields[ $field ] ? 'yes' : '' ) : $fields[ $field ];
		$robots_request->set_param( $target, $value );
		$has_robots = true;
	}
	if ( $has_robots ) {
		$response = rest_do_request( $robots_request );
		if ( $response->is_error() ) {
			$error = new WP_Error( 'wpagent_seopress_write_failed', __( 'SEOPress hat Canonical- oder Robots-Angaben abgelehnt.', 'wp-agent-companion' ), array( 'status' => $response->get_status(), 'response' => $response->get_data() ) );
			return wpagent_seo_error_with_postmeta_rollback( $error, $post_id, $before );
		}
	}

	$social_map = array(
		'open_graph_title'        => '_seopress_social_fb_title',
		'open_graph_description'  => '_seopress_social_fb_desc',
		'twitter_title'           => '_seopress_social_twitter_title',
		'twitter_description'     => '_seopress_social_twitter_desc',
	);
	$social_request = new WP_REST_Request( 'PUT', "/seopress/v1/posts/{$post_id}/social-settings" );
	$has_social     = false;
	foreach ( $social_map as $field => $target ) {
		if ( null === $fields[ $field ] ) {
			continue;
		}
		$social_request->set_param( $target, $fields[ $field ] );
		$has_social = true;
	}
	if ( $has_social ) {
		$response = rest_do_request( $social_request );
		if ( $response->is_error() ) {
			$error = new WP_Error( 'wpagent_seopress_write_failed', __( 'SEOPress hat Social-Meta abgelehnt.', 'wp-agent-companion' ), array( 'status' => $response->get_status(), 'response' => $response->get_data() ) );
			return wpagent_seo_error_with_postmeta_rollback( $error, $post_id, $before );
		}
	}

	$fresh = wpagent_read_seo_meta( $post_id, 'seopress' );
	if ( is_wp_error( $fresh ) ) {
		return wpagent_seo_error_with_postmeta_rollback( $fresh, $post_id, $before );
	}
	$verification = wpagent_verify_seo_snapshot( $fields, $fresh );
	$rolled_back          = false;
	$rollback_verified    = null;
	if ( ! $verification['verified'] ) {
		$rolled_back       = true;
		$rollback_verified = wpagent_restore_seo_postmeta( $post_id, $before );
	}
	return array(
		'post_id'            => $post_id,
		'plugin'             => 'seopress',
		'written'            => $verification['written'],
		'verified'           => $verification['verified'],
		'indexable_verified' => null,
		'rolled_back'        => $rolled_back,
		'rollback_verified'  => $rollback_verified,
	);
}

/**
 * Rebuild one Yoast indexable through Yoast's own container services.
 *
 * Yoast's public REST API is read-only. Its normal wp_insert_post watcher is
 * attempted first by the caller; this guarded fallback mirrors the current
 * watcher implementation and is accepted only when our independent Meta
 * Surface read-back succeeds afterwards.
 */
function wpagent_refresh_yoast_indexable( int $post_id, ?bool $expected_cornerstone = null ): bool {
	if ( ! function_exists( 'YoastSEO' ) ) {
		return false;
	}
	try {
		$classes    = YoastSEO()->classes;
		$repository = $classes->get( 'Yoast\\WP\\SEO\\Repositories\\Indexable_Repository' );
		$builder    = $classes->get( 'Yoast\\WP\\SEO\\Builders\\Indexable_Builder' );
		$existing   = $repository->find_by_id_and_type( $post_id, 'post', false );
		$indexable  = $builder->build_for_id_and_type( $post_id, 'post', $existing );
		if ( ! is_object( $indexable ) ) {
			return false;
		}
		if ( null !== $expected_cornerstone && (bool) ( $indexable->is_cornerstone ?? false ) !== $expected_cornerstone ) {
			return false;
		}

		// Yoast speichert Indexables bewusst nur in Produktionsumgebungen. Wenn
		// sie aktiviert sind, muss zusätzlich der erneut gelesene Datensatz den
		// Cornerstone-Wert bestätigen. Lokal genügt das vom nativen Builder
		// erzeugte Objekt, da Yoast selbst dort keine Tabellenzeile persistiert.
		$helper = YoastSEO()->helpers->indexable ?? null;
		if ( is_object( $helper ) && method_exists( $helper, 'should_index_indexables' ) && $helper->should_index_indexables() ) {
			$persisted = $repository->find_by_id_and_type( $post_id, 'post', false );
			if ( ! is_object( $persisted ) ) {
				return false;
			}
			if ( null !== $expected_cornerstone && (bool) ( $persisted->is_cornerstone ?? false ) !== $expected_cornerstone ) {
				return false;
			}
		}
		return true;
	} catch ( Throwable ) {
		return false;
	}
}

/**
 * @param mixed $input
 * @return array|WP_Error
 */
function wpagent_exec_set_seo_meta( $input ) {
	$input   = is_array( $input ) ? $input : array();
	$post_id = (int) ( $input['post_id'] ?? 0 );

	if ( $post_id <= 0 || ! get_post( $post_id ) ) {
		return new WP_Error( 'wpagent_no_post', __( 'Beitrag nicht gefunden.', 'wp-agent-companion' ), array( 'status' => 404 ) );
	}
	if ( ! current_user_can( 'edit_post', $post_id ) ) {
		return new WP_Error( 'wpagent_forbidden', __( 'Keine Berechtigung.', 'wp-agent-companion' ), array( 'status' => 403 ) );
	}

	$plugin = wpagent_detect_seo_plugin();
	$fields = wpagent_seo_fields_from_input( $input );
	if ( is_wp_error( $fields ) ) {
		return $fields;
	}
	if ( ! wpagent_seo_fields_have_value( $fields ) ) {
		return new WP_Error( 'wpagent_no_fields', __( 'Kein SEO-Feld übergeben.', 'wp-agent-companion' ), array( 'status' => 400 ) );
	}
	if ( 'none' === $plugin ) {
		return new WP_Error( 'wpagent_seo_plugin_missing', __( 'Kein unterstütztes SEO-Plugin aktiv. WPAgently schreibt keine wirkungslosen Ersatz-Metafelder.', 'wp-agent-companion' ), array( 'status' => 409 ) );
	}
	if ( null !== $fields['cornerstone'] && ! in_array( $plugin, array( 'rankmath', 'yoast' ), true ) ) {
		return new WP_Error( 'wpagent_seo_field_unsupported', __( 'Das aktive SEO-Plugin stellt keinen bestätigten Cornerstone-Pfad bereit. WPAgently schreibt deshalb kein wirkungsloses Ersatz-Metafeld.', 'wp-agent-companion' ), array( 'status' => 409, 'field' => 'cornerstone', 'plugin' => $plugin ) );
	}

	$lock = wpagent_seo_acquire_lock( $post_id );
	if ( is_wp_error( $lock ) ) {
		return $lock;
	}
	try {
		return wpagent_exec_set_seo_meta_locked( $post_id, $plugin, $fields );
	} finally {
		wpagent_seo_release_lock( $post_id, $lock );
	}
}

/**
 * Führt den Provider-Write aus, während der aufrufende Wrapper den Post-Lock hält.
 *
 * @param 'rankmath'|'yoast'|'aioseo'|'seopress' $plugin
 * @param array<string,string|bool|null>           $fields
 * @return array|WP_Error
 */
function wpagent_exec_set_seo_meta_locked( int $post_id, string $plugin, array $fields ) {
	if ( 'aioseo' === $plugin ) {
		return wpagent_set_aioseo_meta( $post_id, $fields );
	}
	if ( 'seopress' === $plugin ) {
		return wpagent_set_seopress_meta( $post_id, $fields );
	}
	$map    = array(
		'rankmath' => array(
			'title'                   => 'rank_math_title',
			'description'             => 'rank_math_description',
			'focus_keyword'           => 'rank_math_focus_keyword',
			'canonical_url'           => 'rank_math_canonical_url',
			'open_graph_title'        => 'rank_math_facebook_title',
			'open_graph_description'  => 'rank_math_facebook_description',
			'twitter_title'           => 'rank_math_twitter_title',
			'twitter_description'     => 'rank_math_twitter_description',
		),
		'yoast'    => array(
			'title'                   => '_yoast_wpseo_title',
			'description'             => '_yoast_wpseo_metadesc',
			'focus_keyword'           => '_yoast_wpseo_focuskw',
			'canonical_url'           => '_yoast_wpseo_canonical',
			'open_graph_title'        => '_yoast_wpseo_opengraph-title',
			'open_graph_description'  => '_yoast_wpseo_opengraph-description',
			'twitter_title'           => '_yoast_wpseo_twitter-title',
			'twitter_description'     => '_yoast_wpseo_twitter-description',
		),
	);
	$keys   = $map[ $plugin ];
	$touched_meta_keys = array();
	foreach ( $keys as $field => $meta_key ) {
		if ( null !== $fields[ $field ] ) {
			$touched_meta_keys[] = $meta_key;
		}
	}
	if ( 'rankmath' === $plugin && ( null !== $fields['robots_noindex'] || null !== $fields['robots_nofollow'] ) ) {
		$touched_meta_keys[] = 'rank_math_robots';
	}
	if ( 'rankmath' === $plugin && null !== $fields['cornerstone'] ) {
		$touched_meta_keys[] = 'rank_math_pillar_content';
	}
	if ( 'yoast' === $plugin ) {
		if ( null !== $fields['robots_noindex'] ) {
			$touched_meta_keys[] = '_yoast_wpseo_meta-robots-noindex';
		}
		if ( null !== $fields['robots_nofollow'] ) {
			$touched_meta_keys[] = '_yoast_wpseo_meta-robots-nofollow';
		}
		if ( null !== $fields['cornerstone'] ) {
			$touched_meta_keys[] = '_yoast_wpseo_is_cornerstone';
		}
	}
	$before = wpagent_capture_seo_postmeta( $post_id, $touched_meta_keys );

	foreach ( $keys as $field => $key ) {
		$value = $fields[ $field ];
		if ( null === $value ) {
			continue;
		}
		if ( '' === $value ) {
			delete_post_meta( $post_id, $key );
		} else {
			update_post_meta( $post_id, $key, $value );
		}
	}
	if ( 'rankmath' === $plugin && ( null !== $fields['robots_noindex'] || null !== $fields['robots_nofollow'] ) ) {
		$current = get_post_meta( $post_id, 'rank_math_robots', true );
		$current = is_array( $current ) ? array_values( array_unique( array_map( 'sanitize_key', $current ) ) ) : array();
		$index   = null;
		$follow  = null;
		foreach ( $current as $directive ) {
			if ( in_array( $directive, array( 'index', 'noindex' ), true ) ) {
				$index = $directive;
			} elseif ( in_array( $directive, array( 'follow', 'nofollow' ), true ) ) {
				$follow = $directive;
			}
		}
		if ( null !== $fields['robots_noindex'] ) {
			$index = $fields['robots_noindex'] ? 'noindex' : 'index';
		}
		if ( null !== $fields['robots_nofollow'] ) {
			$follow = $fields['robots_nofollow'] ? 'nofollow' : 'follow';
		}
		$other  = array_values( array_diff( $current, array( 'index', 'noindex', 'follow', 'nofollow' ) ) );
		$robots = array_values( array_filter( array_merge( array( $index, $follow ), $other ) ) );
		update_post_meta( $post_id, 'rank_math_robots', $robots );
	}
	if ( 'rankmath' === $plugin && null !== $fields['cornerstone'] ) {
		if ( $fields['cornerstone'] ) {
			update_post_meta( $post_id, 'rank_math_pillar_content', 'on' );
		} else {
			delete_post_meta( $post_id, 'rank_math_pillar_content' );
		}
	}
	if ( 'yoast' === $plugin ) {
		if ( null !== $fields['robots_noindex'] ) {
			update_post_meta( $post_id, '_yoast_wpseo_meta-robots-noindex', $fields['robots_noindex'] ? '1' : '2' );
		}
		if ( null !== $fields['robots_nofollow'] ) {
			update_post_meta( $post_id, '_yoast_wpseo_meta-robots-nofollow', $fields['robots_nofollow'] ? '1' : '0' );
		}
		if ( null !== $fields['cornerstone'] ) {
			if ( $fields['cornerstone'] ) {
				update_post_meta( $post_id, '_yoast_wpseo_is_cornerstone', '1' );
			} else {
				delete_post_meta( $post_id, '_yoast_wpseo_is_cornerstone' );
			}
		}
	}

	$indexable_verified = null;
	$yoast_output_fields = array_diff_key( $fields, array( 'focus_keyword' => true, 'cornerstone' => true ) );
	$yoast_output_fields = array_filter( $yoast_output_fields, static fn ( $value ): bool => null !== $value );
	$yoast_cornerstone   = null !== $fields['cornerstone'] ? (bool) $fields['cornerstone'] : null;
	$refresh_yoast       = 'yoast' === $plugin && ( ! empty( $yoast_output_fields ) || null !== $yoast_cornerstone );
	if ( $refresh_yoast ) {
		// A regular post update runs Yoast's supported save_post watcher after
		// the new meta values exist. The independent verifier then reads Yoast's
		// effective Meta Surface and prevents a postmeta-only false positive.
		$refresh = wp_update_post( array( 'ID' => $post_id ), true );
		if ( is_wp_error( $refresh ) ) {
			$indexable_verified = false;
		} elseif ( function_exists( 'wpagent_render_verify_check_seo_yoast' ) ) {
			// Vor dem ersten Zugriff auf die Meta Surface neu aufbauen. Die Surface
			// memoisiert ihre Presentation request-lokal; ein vorheriger Lesezugriff
			// würde deshalb auch nach einem späteren Rebuild veraltete Werte liefern.
			$indexable_verified = wpagent_refresh_yoast_indexable( $post_id, $yoast_cornerstone );
			if ( $indexable_verified && ! empty( $yoast_output_fields ) ) {
				$indexable = wpagent_render_verify_check_seo_yoast( $post_id, $yoast_output_fields );
				$indexable_verified = true === ( $indexable['agrees'] ?? false );
			}
		} else {
			$indexable_verified = false;
		}
	}
	$fresh = wpagent_read_seo_meta( $post_id, $plugin );
	if ( is_wp_error( $fresh ) ) {
		return $fresh;
	}
	$verification = wpagent_verify_seo_snapshot( $fields, $fresh );
	foreach ( $verification['written'] as $field => &$field_result ) {
		if ( isset( $keys[ $field ] ) ) {
			$field_result['meta_key'] = $keys[ $field ];
		}
		if ( 'cornerstone' === $field ) {
			$field_result['meta_key'] = 'rankmath' === $plugin ? 'rank_math_pillar_content' : '_yoast_wpseo_is_cornerstone';
		}
	}
	unset( $field_result );
	$verified = $verification['verified'] && ( false !== $indexable_verified );
	$rolled_back       = false;
	$rollback_verified = null;
	if ( ! $verified ) {
		$rolled_back       = true;
		$rollback_verified = wpagent_restore_seo_postmeta( $post_id, $before );
		if ( $refresh_yoast ) {
			$rollback_post = wp_update_post( array( 'ID' => $post_id ), true );
			$rollback_cornerstone = null;
			if ( null !== $yoast_cornerstone ) {
				$rollback_cornerstone = in_array( '1', $before['_yoast_wpseo_is_cornerstone'] ?? array(), true );
			}
			$rollback_verified = $rollback_verified
				&& ! is_wp_error( $rollback_post )
				&& wpagent_refresh_yoast_indexable( $post_id, $rollback_cornerstone );
		}
	}

	return array(
		'post_id'            => $post_id,
		'plugin'             => $plugin,
		'written'            => $verification['written'],
		'verified'           => $verified,
		'indexable_verified' => $indexable_verified,
		'rolled_back'        => $rolled_back,
		'rollback_verified'  => $rollback_verified,
	);
}
