<?php
/** Atomic lock, provider write and rollback for GeneratePress settings. */

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

/** @return string|WP_Error */
function wpagent_generatepress_acquire_lock() {
	$token = wpagent_companion_acquire_option_lock( WPAGENT_GENERATEPRESS_LOCK_OPTION, WPAGENT_GENERATEPRESS_LOCK_TTL );
	if ( is_string( $token ) ) {
		return $token;
	}
	return new WP_Error( 'wpagent_generatepress_locked', __( 'Die GeneratePress-Einstellungen werden gerade durch einen anderen Vorgang geändert.', 'wp-agent-companion' ), array( 'status' => 409 ) );
}

function wpagent_generatepress_release_lock( string $token ): void {
	wpagent_companion_release_option_lock( WPAGENT_GENERATEPRESS_LOCK_OPTION, $token );
}

function wpagent_generatepress_lock_is_owned( string $token ): bool {
	return wpagent_companion_option_lock_is_owned( WPAGENT_GENERATEPRESS_LOCK_OPTION, $token );
}

function wpagent_generatepress_refresh_lock( string $token ): bool {
	return wpagent_companion_refresh_option_lock( WPAGENT_GENERATEPRESS_LOCK_OPTION, $token, WPAGENT_GENERATEPRESS_LOCK_TTL );
}

/** @return true|WP_Error */
function wpagent_generatepress_checkpoint( string $token, bool $write_started = false ) {
	if ( current_user_can( 'manage_options' ) && wpagent_generatepress_refresh_lock( $token ) && wpagent_generatepress_lock_is_owned( $token ) ) {
		return true;
	}
	return new WP_Error(
		$write_started ? 'wpagent_generatepress_lock_lost_after_write' : 'wpagent_generatepress_lock_lost',
		__( 'Die GeneratePress-Änderung hat ihre exklusive Sperre verloren.', 'wp-agent-companion' ),
		array( 'status' => $write_started ? 500 : 409, 'write_started' => $write_started )
	);
}

/** @param array{exists:bool,raw:string,autoload:string} $expected */
function wpagent_generatepress_option_row_matches( string $option_name, array $expected ): bool {
	$row = wpagent_companion_read_option_row_direct( $option_name );
	if ( ! $expected['exists'] ) {
		return ! is_array( $row );
	}
	return is_array( $row ) && hash_equals( $expected['raw'], $row['raw'] ) && hash_equals( $expected['autoload'], $row['autoload'] );
}

/**
 * A conditional primitive retained for a future provider path that can prove a
 * complete candidate. GeneratePress regenerates several coupled cache rows,
 * therefore regular failed writes use recovery_required rather than replaying
 * a stale bundle.
 *
 * @param array{exists:bool,raw:string,autoload:string} $previous
 * @param array{exists:bool,raw:string,autoload:string} $candidate
 */
function wpagent_generatepress_restore_option_candidate( string $option_name, array $previous, array $candidate ): bool {
	global $wpdb;
	if ( ! wpagent_generatepress_option_row_matches( $option_name, $candidate ) ) {
		return false;
	}
	if ( ! $candidate['exists'] && ! $previous['exists'] ) {
		return true;
	}
	if ( $candidate['exists'] && ! $previous['exists'] ) {
		$changed = $wpdb->query( $wpdb->prepare( "DELETE FROM {$wpdb->options} WHERE option_name = %s AND HEX(option_value) = HEX(%s) AND autoload = %s", $option_name, $candidate['raw'], $candidate['autoload'] ) );
	} elseif ( ! $candidate['exists'] ) {
		$changed = $wpdb->query( $wpdb->prepare( "INSERT INTO {$wpdb->options} (option_name, option_value, autoload) SELECT %s, %s, %s WHERE NOT EXISTS (SELECT 1 FROM {$wpdb->options} WHERE option_name = %s)", $option_name, $previous['raw'], $previous['autoload'], $option_name ) );
	} else {
		$changed = $wpdb->query( $wpdb->prepare( "UPDATE {$wpdb->options} SET option_value = %s, autoload = %s WHERE option_name = %s AND HEX(option_value) = HEX(%s) AND autoload = %s", $previous['raw'], $previous['autoload'], $option_name, $candidate['raw'], $candidate['autoload'] ) );
	}
	if ( 1 !== $changed ) {
		return false;
	}
	wpagent_companion_forget_option_cache( $option_name, in_array( $previous['autoload'], wp_autoload_values_to_autoload(), true ) || in_array( $candidate['autoload'], wp_autoload_values_to_autoload(), true ) );
	return wpagent_generatepress_option_row_matches( $option_name, $previous );
}

/** @return WP_Error */
function wpagent_generatepress_recovery_required() {
	$data  = array( 'status' => 502, 'rollback_verified' => false, 'recovery_required' => true );
	$fresh = wpagent_generatepress_snapshot();
	if ( is_array( $fresh ) ) {
		$data['current_hash'] = wpagent_generatepress_state_hash( $fresh );
	}
	return new WP_Error(
		'wpagent_generatepress_recovery_required',
		__( 'GeneratePress hat die Änderung oder den CSS-Cache nicht vollständig bestätigt. Es wurde kein automatisches Rollback ausgeführt, weil ein fremder Theme- oder Cache-Schreibvorgang nicht sicher ausgeschlossen werden kann. Wiederhole den Vorgang nicht automatisch und prüfe den aktuellen Zustand manuell.', 'wp-agent-companion' ),
		$data
	);
}

/** @return WP_Error */
function wpagent_generatepress_fail_after_rollback( array $snapshot, string $previous_hash ) {
	unset( $snapshot, $previous_hash );
	return wpagent_generatepress_recovery_required();
}

/** @return true|WP_Error */
function wpagent_generatepress_write_and_refresh( array $settings, string $token ) {
	$checkpoint = wpagent_generatepress_checkpoint( $token );
	if ( is_wp_error( $checkpoint ) ) {
		return $checkpoint;
	}
	try {
		update_option( 'generate_settings', $settings, false );
		wp_cache_delete( 'generate_settings', 'options' );
		$checkpoint = wpagent_generatepress_checkpoint( $token, true );
		if ( is_wp_error( $checkpoint ) ) {
			return $checkpoint;
		}
		generate_update_dynamic_css_cache();
		wp_cache_delete( 'generate_dynamic_css_output', 'options' );
		wp_cache_delete( 'generate_dynamic_css_cached_version', 'options' );
		$checkpoint = wpagent_generatepress_checkpoint( $token, true );
		if ( is_wp_error( $checkpoint ) ) {
			return $checkpoint;
		}
		if ( generate_get_option( 'dynamic_css_cache' ) ) {
			$expected_css = wp_strip_all_tags( generate_get_dynamic_css() );
			$actual_css   = get_option( 'generate_dynamic_css_output', null );
			if ( ! is_string( $actual_css ) || ! hash_equals( $expected_css, $actual_css ) ) {
				return new WP_Error( 'wpagent_generatepress_cache_unverified', __( 'GeneratePress hat den neu erzeugten CSS-Cache nicht exakt bestätigt.', 'wp-agent-companion' ), array( 'status' => 502 ) );
			}
		}
	} catch ( Throwable $error ) {
		return new WP_Error( 'wpagent_generatepress_provider_failed', __( 'GeneratePress ist beim Speichern oder Erzeugen des CSS-Caches fehlgeschlagen.', 'wp-agent-companion' ), array( 'status' => 502 ) );
	}
	return true;
}
