<?php
/**
 * Ability: wp-agent/update-product
 *
 * Partielles Update eines WooCommerce-Produkts ausschließlich über das
 * WC_Product-Objekt (wc_get_product() + offizielle Setter + save()), nie über
 * wp_update_post()/update_post_meta() direkt.
 *
 * KRITISCH (empirisch verifiziert, nicht die verbreitete HPOS-Behauptung):
 * HPOS (High-Performance Order Storage) betrifft ausschließlich Bestellungen,
 * nicht Produkte. Der reale Fallstrick bei Produkten ist ein anderer: Ein
 * roher update_post_meta( $id, '_regular_price', ... ) ändert zwar die
 * Postmeta-Zeile, aber weder WooCommerce' Objekt-Cache (wc_get_product() kann
 * innerhalb desselben Requests den alten, gecachten Wert zurückgeben) noch die
 * Lookup-Tabelle wp_wc_product_meta_lookup, die den Shop-Frontend für
 * Preis-Sortierung und -Filterung nutzt. Das Ergebnis: der Preis "stimmt" in
 * der DB, aber Sortierung/Filterung im Shop bleiben auf dem alten Stand, ohne
 * jede Fehlermeldung. Nur $product->save() aktualisiert Objekt-Cache und
 * Lookup-Tabelle konsistent mit.
 *
 * Zweiter Fallstrick: WC_Product::set_sku() wirft bei einer doppelt
 * vergebenen SKU SOFORT (nicht erst bei save()) eine WC_Data_Exception, die
 * unabgefangen zu einem PHP-Fatal-Error (HTTP 500) statt zu einer sauberen
 * Fehlermeldung führt. Eine eigene Vorab-Prüfung über
 * wc_get_product_id_by_sku() liefert die verständliche 400-Meldung; das
 * try/catch um den gesamten Schreib-Block fängt zusätzlich jede andere
 * WC-eigene Validierungs-Exception ab, die (noch) nicht vorab geprüft wird.
 *
 * @package WPAgentCompanion
 */

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

function wpagent_ability_update_product(): array {
	$properties = wpagent_wc_product_input_properties( true );
	unset( $properties['type'] );
	$properties['expected_state_hash'] = array( 'type' => 'string', 'pattern' => '^[a-f0-9]{64}$' );
	return array(
		'label'               => __( 'Produkt aktualisieren', 'wp-agent-companion' ),
		'description'         => __( 'Aktualisiert Produktdaten einschließlich Preise, Lager, Medien, Versand, Abmessungen, Taxonomien und Attribute ausschließlich über das WC_Product-Objekt und verifiziert das Ergebnis.', 'wp-agent-companion' ),
		'category'            => 'wp-agent',
		'input_schema'        => array(
			'type'                 => 'object',
			'properties'           => $properties,
			'required'             => array( 'product_id', 'expected_state_hash' ),
			'additionalProperties' => false,
		),
		'output_schema'       => array(
			'type'                 => 'object',
			'properties'           => array(
				'product_id' => wpagent_wc_read_id_schema(),
				'type'       => array( 'type' => 'string', 'enum' => array( 'simple', 'variable', 'grouped', 'external' ) ),
				'written'    => wpagent_wc_product_written_schema(),
				'verified'   => array( 'type' => 'boolean' ),
				'product'    => wpagent_wc_product_read_schema(),
			),
			'required'             => array( 'product_id', 'type', 'written', 'verified', 'product' ),
			'additionalProperties' => false,
		),
		'execute_callback'    => 'wpagent_exec_update_product',
		'permission_callback' => 'wpagent_perm_edit_posts',
		'meta'                => array(
			'show_in_rest' => true,
			'annotations'  => array(
				'readonly'    => false,
				'destructive' => false,
				'idempotent'  => false,
			),
		),
	);
}

/**
 * @param mixed $input
 * @return array|WP_Error
 */
function wpagent_exec_update_product( $input ) {
	$input      = is_array( $input ) ? $input : array();
	$product_id = (int) ( $input['product_id'] ?? 0 );
	$initial = wpagent_get_editable_product( $product_id );
	if ( is_wp_error( $initial ) ) {
		return $initial;
	}
	$lock = wpagent_wc_acquire_product_lock( $product_id );
	if ( is_wp_error( $lock ) ) {
		return $lock;
	}
	$save_started = false;
	try {
		// The permission preflight may have populated WooCommerce's per-request
		// object cache. Re-read after acquiring the scoped lock so CAS compares
		// against the newest provider state rather than that preflight object.
		wpagent_wc_forget_product_cache( $product_id );
		$product = wpagent_get_editable_product( $product_id );
		if ( is_wp_error( $product ) ) {
			return $product;
		}
		$previous = wpagent_wc_product_payload( $product );
		$cas      = wpagent_wc_require_expected_state_hash( $input, $previous, 'product' );
		if ( is_wp_error( $cas ) ) {
			return $cas;
		}
		$changed = array();
		if ( array_key_exists( 'name', $input ) ) {
			$name = trim( (string) $input['name'] );
			if ( '' === $name ) {
				return new WP_Error( 'wpagent_bad_product_value', __( 'Der Produktname darf nicht leer sein.', 'wp-agent-companion' ), array( 'status' => 400 ) );
			}
			$product->set_name( $name );
			$changed[] = 'name';
		}
		if ( array_key_exists( 'status', $input ) ) {
			$status_cap = wpagent_check_post_status_cap( 'product', (string) $input['status'] );
			if ( is_wp_error( $status_cap ) ) return $status_cap;
			$product->set_status( (string) $input['status'] );
			$changed[] = 'status';
		}
		$applied = wpagent_wc_apply_product_fields( $product, $input );
		if ( is_wp_error( $applied ) ) return $applied;
		$changed = array_values( array_unique( array_merge( $changed, $applied ) ) );
		if ( array() === $changed ) return new WP_Error( 'wpagent_no_fields', __( 'Kein zu aktualisierendes Feld übergeben.', 'wp-agent-companion' ), array( 'status' => 400 ) );
		$expected = wpagent_wc_capture_product_fields( $product, $changed );
		if ( ! wpagent_wc_product_lock_checkpoint( $product_id, $lock ) ) {
			return wpagent_wc_recovery_required( 'product', __( 'Die exklusive Produktsperre ging vor dem WooCommerce-Schreibvorgang verloren. Der Zustand bleibt unverändert und muss neu gelesen werden.', 'wp-agent-companion' ), $previous['state_hash'] );
		}
		$save_started = true;
		$product->save();
		if ( ! wpagent_wc_product_lock_checkpoint( $product_id, $lock ) ) {
			return wpagent_wc_recovery_required( 'product', __( 'Die exklusive Produktsperre ging nach dem WooCommerce-Schreibvorgang verloren. Wiederhole ihn nicht automatisch und prüfe den aktuellen Zustand manuell.', 'wp-agent-companion' ) );
		}
		$fresh = wpagent_wc_fresh_product( $product_id );
		if ( ! $fresh instanceof WC_Product ) {
			return wpagent_wc_recovery_required( 'product', __( 'Das aktualisierte Produkt konnte nicht erneut gelesen werden. Wiederhole den Vorgang nicht automatisch und prüfe den aktuellen Zustand manuell.', 'wp-agent-companion' ) );
		}
		$verification = wpagent_wc_verify_product_fields( $fresh, $expected );
		if ( ! $verification['verified'] ) {
			return wpagent_wc_recovery_required( 'product', __( 'Das Produkt weicht nach dem WooCommerce-Schreibvorgang vom bestätigten Zielzustand ab. Es wurde kein automatisches Rollback ausgeführt. Prüfe den aktuellen Zustand manuell.', 'wp-agent-companion' ), wpagent_wc_product_payload( $fresh )['state_hash'] );
		}
		if ( ! wpagent_wc_product_lock_checkpoint( $product_id, $lock ) ) {
			return wpagent_wc_recovery_required( 'product', __( 'Die exklusive Produktsperre ging während der Read-back-Prüfung verloren. Wiederhole den Vorgang nicht automatisch und prüfe den aktuellen Zustand manuell.', 'wp-agent-companion' ) );
		}
		$readback = wpagent_wc_read_product_payload( $fresh );
		if ( is_wp_error( $readback ) ) {
			return wpagent_wc_recovery_required( 'product', __( 'Das aktualisierte Produkt verletzt den sicheren Lesevertrag. Es wurde kein automatisches Rollback ausgeführt. Prüfe den aktuellen Zustand manuell.', 'wp-agent-companion' ) );
		}
		return array(
			'product_id' => $product_id,
			'type'       => $fresh->get_type(),
			'written'    => $verification['written'],
			'verified'   => true,
			'product'    => $readback,
		);
	} catch ( WC_Data_Exception $e ) {
		// set_sku()/save() können bei WooCommerce-eigenen Validierungsfehlern
		// synchron eine WC_Data_Exception werfen (empirisch verifiziert: ohne
		// dieses catch führt bereits ein simpler Doppel-SKU-Aufruf zu einem
		// unabgefangenen PHP-Fatal-Error/HTTP 500 statt einer sauberen 400).
		if ( $save_started ) {
			return wpagent_wc_recovery_required( 'product', __( 'WooCommerce hat den Produkt-Schreibvorgang nach seinem Start abgebrochen. Wiederhole ihn nicht automatisch und prüfe den aktuellen Zustand manuell.', 'wp-agent-companion' ) );
		}
		return new WP_Error( 'wpagent_wc_invalid', $e->getMessage(), array( 'status' => 400 ) );
	} catch ( Throwable $error ) {
		return $save_started
			? wpagent_wc_recovery_required( 'product', __( 'WooCommerce hat beim Produkt-Schreibvorgang einen Fehler ausgelöst. Wiederhole ihn nicht automatisch und prüfe den aktuellen Zustand manuell.', 'wp-agent-companion' ) )
			: new WP_Error( 'wpagent_wc_invalid', __( 'Die Produktdaten konnten nicht sicher vorbereitet werden.', 'wp-agent-companion' ), array( 'status' => 400 ) );
	} finally {
		wpagent_wc_release_product_lock( $product_id, $lock );
	}
}
