<?php
/**
 * Abilities für WooCommerce-Produktvarianten.
 *
 * @package WPAgentCompanion
 */

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

function wpagent_ability_list_product_variations(): array {
	return array(
		'label' => __( 'Produktvarianten auflisten', 'wp-agent-companion' ), 'description' => __( 'Listet die Varianten eines variablen WooCommerce-Produkts auf.', 'wp-agent-companion' ), 'category' => 'wp-agent',
		'input_schema' => array( 'type' => 'object', 'properties' => array( 'product_id' => wpagent_wc_read_id_schema(), 'page' => array( 'type' => 'integer', 'minimum' => 1, 'maximum' => WPAGENT_WC_READ_MAX_PAGE ), 'per_page' => array( 'type' => 'integer', 'minimum' => 1, 'maximum' => WPAGENT_WC_READ_MAX_CATALOG_ITEMS ) ), 'required' => array( 'product_id' ), 'additionalProperties' => false ),
		'output_schema' => wpagent_wc_variation_catalog_read_schema(),
		'execute_callback' => 'wpagent_exec_list_product_variations', 'permission_callback' => 'wpagent_perm_edit_posts',
		'meta' => array( 'show_in_rest' => true, 'annotations' => array( 'readonly' => true, 'destructive' => false, 'idempotent' => true ) ),
	);
}

function wpagent_ability_upsert_product_variation(): array {
	return array(
		'label' => __( 'Produktvariante anlegen oder aktualisieren', 'wp-agent-companion' ), 'description' => __( 'Legt eine Produktvariante an oder aktualisiert sie über WC_Product_Variation und verifiziert das Ergebnis.', 'wp-agent-companion' ), 'category' => 'wp-agent',
		'input_schema' => array( 'type' => 'object', 'properties' => array(
			'product_id' => array( 'type' => 'integer', 'minimum' => 1 ), 'variation_id' => array( 'type' => 'integer', 'minimum' => 1 ), 'expected_state_hash' => array( 'type' => 'string', 'pattern' => '^[a-f0-9]{64}$' ), 'attributes' => array( 'type' => 'object', 'minProperties' => 1, 'maxProperties' => 50, 'additionalProperties' => array( 'type' => 'string', 'minLength' => 1, 'maxLength' => 200 ) ),
			'status' => array( 'type' => 'string', 'enum' => array( 'draft', 'publish', 'private' ) ), 'sku' => array( 'type' => 'string', 'maxLength' => 191 ), 'regular_price' => array( 'type' => 'string', 'maxLength' => 32 ), 'sale_price' => array( 'type' => 'string', 'maxLength' => 32 ),
			'manage_stock' => array( 'type' => 'boolean' ), 'stock_quantity' => array( 'type' => 'integer' ), 'stock_status' => array( 'type' => 'string', 'enum' => array( 'instock', 'outofstock', 'onbackorder' ) ),
			'virtual' => array( 'type' => 'boolean' ), 'downloadable' => array( 'type' => 'boolean' ), 'weight' => array( 'type' => 'string', 'maxLength' => 32 ), 'length' => array( 'type' => 'string', 'maxLength' => 32 ), 'width' => array( 'type' => 'string', 'maxLength' => 32 ), 'height' => array( 'type' => 'string', 'maxLength' => 32 ), 'image_id' => array( 'type' => 'integer', 'minimum' => 0 ), 'description' => array( 'type' => 'string', 'maxLength' => 10000 ),
		), 'required' => array( 'product_id' ), 'additionalProperties' => false ),
		'output_schema' => array(
			'type'                 => 'object',
			'properties'           => array(
				'variation' => wpagent_wc_variation_read_schema(),
				'written'   => wpagent_wc_variation_written_schema(),
				'verified'  => array( 'type' => 'boolean' ),
			),
			'required'             => array( 'variation', 'written', 'verified' ),
			'additionalProperties' => false,
		),
		'execute_callback' => 'wpagent_exec_upsert_product_variation', 'permission_callback' => 'wpagent_perm_edit_posts',
		'meta' => array( 'show_in_rest' => true, 'annotations' => array( 'readonly' => false, 'destructive' => false, 'idempotent' => false ) ),
	);
}

function wpagent_ability_delete_product_variation(): array {
	return array(
		'label' => __( 'Produktvariante in den Papierkorb verschieben', 'wp-agent-companion' ), 'description' => __( 'Verschiebt eine Produktvariante reversibel in den Papierkorb. Endgültiges Löschen ist über diesen Remote-Pfad nicht verfügbar. confirm=true ist zwingend erforderlich.', 'wp-agent-companion' ), 'category' => 'wp-agent',
		'input_schema' => array( 'type' => 'object', 'properties' => array( 'product_id' => array( 'type' => 'integer', 'minimum' => 1 ), 'variation_id' => array( 'type' => 'integer', 'minimum' => 1 ), 'expected_state_hash' => array( 'type' => 'string', 'pattern' => '^[a-f0-9]{64}$' ), 'confirm' => array( 'type' => 'boolean' ), 'force' => array( 'type' => 'boolean' ), 'permanent' => array( 'type' => 'boolean' ) ), 'required' => array( 'product_id', 'variation_id', 'expected_state_hash', 'confirm' ), 'additionalProperties' => false ),
		'output_schema' => array( 'type' => 'object', 'properties' => array( 'variation_id' => array( 'type' => 'integer' ), 'trashed' => array( 'type' => 'boolean' ) ) ),
		'execute_callback' => 'wpagent_exec_delete_product_variation', 'permission_callback' => 'wpagent_perm_edit_posts',
		'meta' => array( 'show_in_rest' => true, 'annotations' => array( 'readonly' => false, 'destructive' => true, 'idempotent' => false ) ),
	);
}

/** @return WC_Product_Variable|WP_Error */
function wpagent_get_editable_variable_product( int $product_id ) {
	$product = wpagent_get_editable_product( $product_id );
	if ( is_wp_error( $product ) ) return $product;
	if ( ! $product instanceof WC_Product_Variable ) return new WP_Error( 'wpagent_not_variable', __( 'Das Produkt ist kein variables Produkt.', 'wp-agent-companion' ), array( 'status' => 400 ) );
	return $product;
}

/** @return array|WP_Error */
function wpagent_exec_list_product_variations( $input ) {
	$input = is_array( $input ) ? $input : array();
	$product = wpagent_get_editable_variable_product( (int) ( $input['product_id'] ?? 0 ) );
	if ( is_wp_error( $product ) ) return $product;
	$pagination = wpagent_wc_read_pagination_input( $input );
	if ( is_wp_error( $pagination ) ) return $pagination;
	$result = ( new WC_Product_Query( array(
		'parent'   => $product->get_id(),
		'type'     => 'variation',
		'status'   => 'any',
		'limit'    => $pagination['per_page'],
		'page'     => $pagination['page'],
		'paginate' => true,
		'orderby'  => 'menu_order',
		'order'    => 'ASC',
	) ) )->get_products();
	if ( ! is_object( $result ) || ! isset( $result->products, $result->total, $result->max_num_pages ) ) {
		return wpagent_wc_read_contract_error( 'mismatch', 'variations' );
	}
	return wpagent_wc_read_variation_catalog_payload( $result->products, $result->total, $result->max_num_pages, $pagination['per_page'], $product->get_id() );
}

/** @return array|WP_Error */
function wpagent_exec_upsert_product_variation( $input ) {
	$input = is_array( $input ) ? $input : array();
	$product_id = (int) ( $input['product_id'] ?? 0 );
	$parent = wpagent_get_editable_variable_product( $product_id );
	if ( is_wp_error( $parent ) ) return $parent;
	$variation_id = (int) ( $input['variation_id'] ?? 0 );
	$creating = 0 === $variation_id;
	$lock = wpagent_wc_acquire_product_lock( $product_id );
	if ( is_wp_error( $lock ) ) return $lock;
	$save_started = false;
	try {
		// Refresh the parent and (for updates) child after taking the lock. The
		// initial permission preflight must not supply the value compared by CAS.
		wpagent_wc_forget_product_cache( $product_id );
		if ( $variation_id > 0 ) {
			wpagent_wc_forget_product_cache( $variation_id );
		}
		$parent = wpagent_get_editable_variable_product( $product_id );
		if ( is_wp_error( $parent ) ) return $parent;
		if ( $variation_id > 0 ) {
			$variation = wc_get_product( $variation_id );
			if ( ! $variation instanceof WC_Product_Variation || $variation->get_parent_id() !== $product_id ) return new WP_Error( 'wpagent_no_variation', __( 'Produktvariante nicht gefunden.', 'wp-agent-companion' ), array( 'status' => 404 ) );
			$previous = wpagent_wc_variation_payload( $variation );
			$cas = wpagent_wc_require_expected_state_hash( $input, $previous, 'variation' );
			if ( is_wp_error( $cas ) ) return $cas;
		} else {
			if ( ! isset( $input['attributes'] ) || ! is_array( $input['attributes'] ) || array() === $input['attributes'] ) return new WP_Error( 'wpagent_bad_attributes', __( 'Eine neue Variante benötigt mindestens ein Attribut.', 'wp-agent-companion' ), array( 'status' => 400 ) );
			$variation = new WC_Product_Variation();
			$variation->set_parent_id( $product_id );
		}
		$changed = array();
		$normalized_attributes = null;
		if ( array_key_exists( 'attributes', $input ) ) {
			$normalized_attributes = wpagent_wc_normalize_variation_attributes( $parent, $input['attributes'] );
			if ( is_wp_error( $normalized_attributes ) ) return $normalized_attributes;
			$variation->set_attributes( $normalized_attributes );
			$changed[] = 'attributes';
		}
		if ( array_key_exists( 'sku', $input ) ) {
			$sku = (string) $input['sku'];
			$valid = wpagent_wc_validate_sku( $sku, $variation_id );
			if ( is_wp_error( $valid ) ) return $valid;
			$variation->set_sku( $sku );
			$changed[] = 'sku';
		}
		foreach ( array( 'regular_price', 'sale_price' ) as $field ) {
			if ( ! array_key_exists( $field, $input ) ) continue;
			$value = wpagent_sanitize_wc_price( $input[ $field ], $field );
			if ( is_wp_error( $value ) ) return $value;
			$variation->{'set_' . $field}( $value );
			$changed[] = $field;
		}
		foreach ( array( 'weight', 'length', 'width', 'height' ) as $field ) {
			if ( ! array_key_exists( $field, $input ) ) continue;
			$value = wpagent_sanitize_wc_dimension( $input[ $field ], $field );
			if ( is_wp_error( $value ) ) return $value;
			$variation->{'set_' . $field}( $value );
			$changed[] = $field;
		}
		foreach ( array( 'manage_stock', 'virtual', 'downloadable' ) as $field ) {
			if ( ! array_key_exists( $field, $input ) ) continue;
			$variation->{'set_' . $field}( (bool) $input[ $field ] );
			$changed[] = $field;
		}
		if ( array_key_exists( 'stock_quantity', $input ) ) {
			$variation->set_stock_quantity( (int) $input['stock_quantity'] );
			$changed[] = 'stock_quantity';
		}
		if ( array_key_exists( 'stock_status', $input ) ) {
			$variation->set_stock_status( (string) $input['stock_status'] );
			$changed[] = 'stock_status';
		}
		if ( array_key_exists( 'image_id', $input ) ) {
			$image_id = (int) $input['image_id'];
			$valid = wpagent_wc_validate_image_id( $image_id, 'image_id' );
			if ( is_wp_error( $valid ) ) return $valid;
			$variation->set_image_id( $image_id );
			$changed[] = 'image_id';
		}
		if ( array_key_exists( 'description', $input ) ) {
			$variation->set_description( (string) $input['description'] );
			$changed[] = 'description';
		}
		$status = array_key_exists( 'status', $input ) ? (string) $input['status'] : ( $creating ? 'draft' : null );
		if ( null !== $status ) {
			$status_cap = wpagent_check_post_status_cap( 'product', $status );
			if ( is_wp_error( $status_cap ) ) return $status_cap;
			$variation->set_status( $status );
			$changed[] = 'status';
		}
		if ( ! $creating && array() === $changed ) {
			return new WP_Error( 'wpagent_no_fields', __( 'Kein zu aktualisierendes Feld übergeben.', 'wp-agent-companion' ), array( 'status' => 400 ) );
		}
		if ( is_array( $normalized_attributes ) ) {
			$duplicate = wpagent_wc_find_duplicate_variation( $parent, $normalized_attributes );
			if ( is_wp_error( $duplicate ) ) return $duplicate;
			if ( $duplicate > 0 && ( $creating || $duplicate !== $variation_id ) ) {
				return new WP_Error( 'wpagent_duplicate_variation', __( 'Eine Variante mit genau dieser Attributkombination existiert bereits. Lies sie neu statt die Anlage zu wiederholen.', 'wp-agent-companion' ), array( 'status' => 409, 'variation_id' => $duplicate ) );
			}
		}
		$verify_fields = $creating ? array_values( array_unique( array_merge( array( 'parent_id' ), $changed ) ) ) : $changed;
		$expected = wpagent_wc_capture_variation_fields( $variation, $verify_fields );
		if ( ! wpagent_wc_product_lock_checkpoint( $product_id, $lock ) ) {
			return wpagent_wc_recovery_required( 'variation', __( 'Die exklusive Produktsperre ging vor dem Varianten-Schreibvorgang verloren. Der Zustand bleibt unverändert und muss neu gelesen werden.', 'wp-agent-companion' ), isset( $previous ) ? $previous['state_hash'] : '' );
		}
		$save_started = true;
		$variation_id = $variation->save();
		if ( ! wpagent_wc_product_lock_checkpoint( $product_id, $lock ) ) {
			return wpagent_wc_recovery_required( 'variation', __( 'Die exklusive Produktsperre ging nach dem Varianten-Schreibvorgang verloren. Wiederhole ihn nicht automatisch und prüfe den aktuellen Zustand manuell.', 'wp-agent-companion' ) );
		}
		WC_Product_Variable::sync( $product_id );
		$fresh = wpagent_wc_fresh_product( $variation_id );
		if ( ! $fresh instanceof WC_Product_Variation || (int) $fresh->get_parent_id() !== $product_id ) {
			return wpagent_wc_recovery_required( 'variation', __( 'Die Produktvariante konnte nach dem Schreibvorgang nicht sicher erneut gelesen werden. Wiederhole ihn nicht automatisch und prüfe den aktuellen Zustand manuell.', 'wp-agent-companion' ) );
		}
		$verification = wpagent_wc_verify_variation_fields( $fresh, $expected );
		if ( ! $verification['verified'] ) {
			return wpagent_wc_recovery_required( 'variation', __( 'Die Produktvariante 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_variation_payload( $fresh )['state_hash'] );
		}
		if ( ! wpagent_wc_product_lock_checkpoint( $product_id, $lock ) ) {
			return wpagent_wc_recovery_required( 'variation', __( 'Die exklusive Produktsperre ging während der Varianten-Read-back-Prüfung verloren. Wiederhole den Vorgang nicht automatisch und prüfe den aktuellen Zustand manuell.', 'wp-agent-companion' ) );
		}
		$readback = wpagent_wc_read_variation_payload( $fresh );
		if ( is_wp_error( $readback ) ) {
			return wpagent_wc_recovery_required( 'variation', __( 'Die aktualisierte Produktvariante verletzt den sicheren Lesevertrag. Es wurde kein automatisches Rollback ausgeführt. Prüfe den aktuellen Zustand manuell.', 'wp-agent-companion' ) );
		}
		return array(
			'variation' => $readback,
			'written'   => $verification['written'],
			'verified'  => true,
		);
	} catch ( WC_Data_Exception $e ) {
		if ( $save_started ) {
			return wpagent_wc_recovery_required( 'variation', __( 'WooCommerce hat den Varianten-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( 'variation', __( 'WooCommerce hat beim Varianten-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 Variantenwerte konnten nicht sicher vorbereitet werden.', 'wp-agent-companion' ), array( 'status' => 400 ) );
	} finally {
		wpagent_wc_release_product_lock( $product_id, $lock );
	}
}

/** @return array|WP_Error */
function wpagent_exec_delete_product_variation( $input ) {
	$input = is_array( $input ) ? $input : array();
	$product_id = (int) ( $input['product_id'] ?? 0 ); $variation_id = (int) ( $input['variation_id'] ?? 0 );
	$parent = wpagent_get_editable_variable_product( $product_id ); if ( is_wp_error( $parent ) ) return $parent;
	$variation = wc_get_product( $variation_id );
	if ( ! $variation instanceof WC_Product_Variation || $variation->get_parent_id() !== $product_id ) return new WP_Error( 'wpagent_no_variation', __( 'Produktvariante nicht gefunden.', 'wp-agent-companion' ), array( 'status' => 404 ) );
	if ( ! current_user_can( 'delete_post', $variation_id ) ) return new WP_Error( 'wpagent_forbidden', __( 'Keine Berechtigung.', 'wp-agent-companion' ), array( 'status' => 403 ) );
	if ( true === ( $input['force'] ?? false ) || true === ( $input['permanent'] ?? false ) ) return new WP_Error( 'wpagent_wc_permanent_delete_unavailable', __( 'Das endgültige Löschen von WooCommerce-Produktvarianten ist über diesen Remote-Pfad nicht verfügbar. Verschiebe die Variante stattdessen reversibel in den Papierkorb.', 'wp-agent-companion' ), array( 'status' => 409 ) );
	if ( ! EMPTY_TRASH_DAYS ) return new WP_Error( 'wpagent_wc_variation_trash_unavailable', __( 'Der WordPress-Papierkorb ist deaktiviert. WPAgently führt deshalb keine potenziell endgültige Variantenlöschung aus.', 'wp-agent-companion' ), array( 'status' => 409 ) );
	if ( true !== ( $input['confirm'] ?? false ) ) return new WP_Error( 'wpagent_confirmation_required', __( 'Zum Verschieben in den Papierkorb ist confirm=true erforderlich.', 'wp-agent-companion' ), array( 'status' => 400 ) );
	$lock = wpagent_wc_acquire_product_lock( $product_id );
	if ( is_wp_error( $lock ) ) return $lock;
	$delete_started = false;
	try {
		wpagent_wc_forget_product_cache( $product_id );
		wpagent_wc_forget_product_cache( $variation_id );
		$parent = wpagent_get_editable_variable_product( $product_id );
		if ( is_wp_error( $parent ) ) return $parent;
		$variation = wc_get_product( $variation_id );
		if ( ! $variation instanceof WC_Product_Variation || (int) $variation->get_parent_id() !== $product_id ) return new WP_Error( 'wpagent_no_variation', __( 'Produktvariante nicht gefunden.', 'wp-agent-companion' ), array( 'status' => 404 ) );
		$previous = wpagent_wc_variation_payload( $variation );
		$cas = wpagent_wc_require_expected_state_hash( $input, $previous, 'variation' );
		if ( is_wp_error( $cas ) ) return $cas;
		if ( 'trash' === $variation->get_status() ) return new WP_Error( 'wpagent_wc_variation_already_trashed', __( 'Die Produktvariante befindet sich bereits im Papierkorb. WPAgently führt keinen weiteren Löschvorgang aus.', 'wp-agent-companion' ), array( 'status' => 409, 'current_state_hash' => $previous['state_hash'] ) );
		if ( ! wpagent_wc_product_lock_checkpoint( $product_id, $lock ) ) {
			return wpagent_wc_recovery_required( 'variation', __( 'Die exklusive Produktsperre ging vor dem Varianten-Papierkorb-Vorgang verloren. Der Zustand bleibt unverändert und muss neu gelesen werden.', 'wp-agent-companion' ), $previous['state_hash'] );
		}
		// WooCommerce exposes no conditional trash operation. Re-read both the
		// parent relation and the exact child state immediately before trashing.
		do_action( 'wpagent_wc_before_variation_trash_preflight', $product_id, $variation_id );
		$parent = wpagent_wc_fresh_product( $product_id );
		if ( ! $parent instanceof WC_Product_Variable ) {
			return wpagent_wc_recovery_required( 'variation', __( 'Das Elternprodukt konnte unmittelbar vor dem Varianten-Papierkorb-Vorgang nicht sicher erneut gelesen werden. Wiederhole ihn nicht automatisch und prüfe den aktuellen Zustand manuell.', 'wp-agent-companion' ), $previous['state_hash'] );
		}
		$variation = wpagent_wc_fresh_product( $variation_id );
		if ( ! $variation instanceof WC_Product_Variation || (int) $variation->get_parent_id() !== $product_id ) {
			return wpagent_wc_recovery_required( 'variation', __( 'Die Produktvariante konnte unmittelbar vor dem Papierkorb-Vorgang nicht sicher erneut gelesen werden. Wiederhole ihn nicht automatisch und prüfe den aktuellen Zustand manuell.', 'wp-agent-companion' ), $previous['state_hash'] );
		}
		$pre_trash = wpagent_wc_variation_payload( $variation );
		$cas       = wpagent_wc_require_expected_state_hash( $input, $pre_trash, 'variation' );
		if ( is_wp_error( $cas ) ) {
			return $cas;
		}
		if ( 'trash' === $variation->get_status() ) {
			return new WP_Error( 'wpagent_wc_variation_already_trashed', __( 'Die Produktvariante befindet sich bereits im Papierkorb. WPAgently führt keinen weiteren Löschvorgang aus.', 'wp-agent-companion' ), array( 'status' => 409, 'current_state_hash' => $pre_trash['state_hash'] ) );
		}
		if ( ! current_user_can( 'delete_post', $variation_id ) ) {
			return new WP_Error( 'wpagent_forbidden', __( 'Keine Berechtigung.', 'wp-agent-companion' ), array( 'status' => 403 ) );
		}
		if ( ! wpagent_wc_product_lock_checkpoint( $product_id, $lock ) ) {
			return wpagent_wc_recovery_required( 'variation', __( 'Die exklusive Produktsperre ging unmittelbar vor dem Varianten-Papierkorb-Vorgang verloren. Der Zustand bleibt unverändert und muss neu gelesen werden.', 'wp-agent-companion' ), $pre_trash['state_hash'] );
		}
		$delete_started = true;
		$trashed = $variation->delete();
		if ( ! wpagent_wc_product_lock_checkpoint( $product_id, $lock ) ) {
			return wpagent_wc_recovery_required( 'variation', __( 'Die exklusive Produktsperre ging nach dem Varianten-Papierkorb-Vorgang verloren. Wiederhole ihn nicht automatisch und prüfe den aktuellen Zustand manuell.', 'wp-agent-companion' ) );
		}
		WC_Product_Variable::sync( $product_id );
		wpagent_wc_forget_product_cache( $variation_id );
		wpagent_wc_forget_product_cache( $product_id );
		$post = get_post( $variation_id );
		if ( true !== $trashed || ! $post instanceof WP_Post || 'trash' !== $post->post_status ) {
			return wpagent_wc_recovery_required( 'variation', __( 'Die Produktvariante konnte nach dem Papierkorb-Vorgang nicht sicher als im Papierkorb verifiziert werden. Wiederhole ihn nicht automatisch und prüfe den aktuellen Zustand manuell.', 'wp-agent-companion' ) );
		}
		return array( 'variation_id' => $variation_id, 'trashed' => true );
	} catch ( Throwable $error ) {
		return $delete_started
			? wpagent_wc_recovery_required( 'variation', __( 'WooCommerce hat beim Varianten-Papierkorb-Vorgang 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 Produktvariante konnte nicht sicher für den Papierkorb vorbereitet werden.', 'wp-agent-companion' ), array( 'status' => 400 ) );
	} finally {
		wpagent_wc_release_product_lock( $product_id, $lock );
	}
}

/** @return array<string, string>|WP_Error */
function wpagent_wc_normalize_variation_attributes( WC_Product_Variable $parent, $raw ) {
	if ( ! is_array( $raw ) || array_is_list( $raw ) || count( $raw ) > 50 ) return new WP_Error( 'wpagent_bad_attributes', __( 'attributes muss ein begrenztes Objekt sein.', 'wp-agent-companion' ), array( 'status' => 400 ) );
	$definitions = array();
	foreach ( $parent->get_attributes() as $attribute ) {
		if ( ! $attribute instanceof WC_Product_Attribute || ! $attribute->get_variation() ) continue;
		$key = $attribute->is_taxonomy() ? $attribute->get_name() : sanitize_title( $attribute->get_name() );
		$options = $attribute->is_taxonomy() ? wc_get_product_terms( $parent->get_id(), $attribute->get_name(), array( 'fields' => 'slugs' ) ) : array_map( 'sanitize_title', $attribute->get_options() );
		$lookup = sanitize_title( $key );
		if ( isset( $definitions[ $lookup ] ) ) return new WP_Error( 'wpagent_bad_attributes', __( 'Das Elternprodukt enthält keine eindeutig normalisierbaren Variantenattribute.', 'wp-agent-companion' ), array( 'status' => 400 ) );
		$definitions[ $lookup ] = array( 'key' => $key, 'options' => $options );
	}
	if ( array() === $definitions || count( $raw ) !== count( $definitions ) ) {
		return new WP_Error( 'wpagent_bad_attributes', __( 'attributes muss genau alle Variantenattribute des Elternprodukts enthalten.', 'wp-agent-companion' ), array( 'status' => 400 ) );
	}
	$result = array();
	$provided = array();
	foreach ( $raw as $raw_key => $raw_value ) {
		if ( ! is_string( $raw_key ) || ! is_string( $raw_value ) || strlen( $raw_key ) > 200 || strlen( $raw_value ) > 200 ) return new WP_Error( 'wpagent_bad_attributes', __( 'Jeder Variantenattributname und -wert muss ein begrenzter Text sein.', 'wp-agent-companion' ), array( 'status' => 400 ) );
		$lookup = sanitize_title( (string) $raw_key );
		if ( ! isset( $definitions[ $lookup ] ) ) return new WP_Error( 'wpagent_bad_attributes', sprintf( /* translators: %s: Attributname. */ __( 'Das Variantenattribut "%s" ist am Elternprodukt nicht freigegeben.', 'wp-agent-companion' ), (string) $raw_key ), array( 'status' => 400 ) );
		if ( isset( $provided[ $lookup ] ) ) return new WP_Error( 'wpagent_bad_attributes', __( 'Jedes Variantenattribut darf nur einmal übergeben werden.', 'wp-agent-companion' ), array( 'status' => 400 ) );
		$value = sanitize_title( (string) $raw_value );
		if ( '' === $value || ! in_array( $value, $definitions[ $lookup ]['options'], true ) ) return new WP_Error( 'wpagent_bad_attributes', sprintf( /* translators: %s: Attributwert. */ __( 'Der Attributwert "%s" ist am Elternprodukt nicht vorhanden.', 'wp-agent-companion' ), (string) $raw_value ), array( 'status' => 400 ) );
		$provided[ $lookup ] = true;
		$result[ $definitions[ $lookup ]['key'] ] = $value;
	}
	if ( array() !== array_diff_key( $definitions, $provided ) || array() !== array_diff_key( $provided, $definitions ) ) {
		return new WP_Error( 'wpagent_bad_attributes', __( 'attributes muss genau alle Variantenattribute des Elternprodukts enthalten.', 'wp-agent-companion' ), array( 'status' => 400 ) );
	}
	return $result;
}

/**
 * Returns an existing exact attribute combination, never a wildcard match.
 *
 * WooCommerce's native find_matching_product_variation() intentionally considers
 * an empty stored attribute a storefront wildcard and reads published variants
 * only. The agent contract instead compares complete, literal attribute maps,
 * including drafts. Snapshot the native IDs once, then hydrate those exact
 * IDs in bounded batches. A final snapshot prevents offset pagination from
 * silently skipping a variation when an external native writer removes and
 * inserts a row with the same overall count. Without a provider index for
 * literal all-status maps, a complete duplicate check is necessarily O(n).
 * The Companion product lock serializes only Companion calls. Native writes
 * after one variation was hydrated cannot be made globally conditional by Woo.
 *
 * @return int|WP_Error
 */
function wpagent_wc_find_duplicate_variation( WC_Product_Variable $parent, array $attributes ) {
	$target = wpagent_wc_canonicalize_state( $attributes );
	$ids = wpagent_wc_variation_id_snapshot( (int) $parent->get_id() );
	if ( is_wp_error( $ids ) ) {
		return $ids;
	}
	foreach ( array_chunk( $ids, WPAGENT_WC_VARIATION_DUPLICATE_PAGE_SIZE ) as $batch ) {
		$result = ( new WC_Product_Query( array(
			'parent'   => (int) $parent->get_id(),
			'type'     => 'variation',
			'status'   => 'any',
			'include'  => $batch,
			'limit'    => count( $batch ),
			'return'   => 'objects',
			'orderby'  => 'include',
		) ) )->get_products();
		if ( ! is_array( $result ) || count( $result ) !== count( $batch ) ) {
			return wpagent_wc_variation_duplicate_scan_drift();
		}
		$seen = array();
		foreach ( $result as $variation ) {
			$variation_id = $variation instanceof WC_Product_Variation ? (int) $variation->get_id() : 0;
			if ( $variation_id < 1 || ! in_array( $variation_id, $batch, true ) || isset( $seen[ $variation_id ] ) || (int) $variation->get_parent_id() !== (int) $parent->get_id() ) {
				return new WP_Error( 'wpagent_wc_variation_duplicate_scan_failed', __( 'Die vorhandenen Produktvarianten konnten nicht sicher auf Dubletten geprüft werden.', 'wp-agent-companion' ), array( 'status' => 500 ) );
			}
			$seen[ $variation_id ] = true;
			if ( wpagent_wc_canonicalize_state( $variation->get_attributes() ) === $target ) {
				return $variation_id;
			}
		}
	}
	$after_ids = wpagent_wc_variation_id_snapshot( (int) $parent->get_id() );
	if ( is_wp_error( $after_ids ) ) {
		return $after_ids;
	}
	if ( $ids !== $after_ids ) {
		return wpagent_wc_variation_duplicate_scan_drift();
	}
	return 0;
}

/** @return list<int>|WP_Error */
function wpagent_wc_variation_id_snapshot( int $product_id ) {
	$ids = wc_get_products(
		array(
			'parent'  => $product_id,
			'type'    => 'variation',
			'status'  => 'any',
			'limit'   => -1,
			'return'  => 'ids',
			'orderby' => 'ID',
			'order'   => 'ASC',
		)
	);
	if ( ! is_array( $ids ) ) {
		return new WP_Error( 'wpagent_wc_variation_duplicate_scan_failed', __( 'Die vorhandenen Produktvarianten konnten nicht sicher auf Dubletten geprüft werden.', 'wp-agent-companion' ), array( 'status' => 500 ) );
	}
	$previous_id = 0;
	foreach ( $ids as $index => $id ) {
		$numeric_id = is_int( $id ) ? $id : ( is_string( $id ) && ctype_digit( $id ) ? (int) $id : 0 );
		if ( $numeric_id < 1 || $numeric_id <= $previous_id ) {
			return new WP_Error( 'wpagent_wc_variation_duplicate_scan_failed', __( 'Die vorhandenen Produktvarianten konnten nicht sicher auf Dubletten geprüft werden.', 'wp-agent-companion' ), array( 'status' => 500 ) );
		}
		$ids[ $index ] = $numeric_id;
		$previous_id  = $numeric_id;
	}
	return $ids;
}

/** @return WP_Error */
function wpagent_wc_variation_duplicate_scan_drift(): WP_Error {
	return new WP_Error( 'wpagent_wc_variation_duplicate_scan_drift', __( 'Die Variantenmenge änderte sich während des Dublettenvergleichs. Der Schreibvorgang wurde vor der Mutation abgebrochen. Lies das Produkt neu und prüfe die Variante manuell.', 'wp-agent-companion' ), array( 'status' => 409 ) );
}
