<?php
/**
 * Guarded bulk sale-price updates for one WooCommerce category or product tag.
 *
 * This is deliberately a preview/confirm flow. A preview freezes the exact
 * item IDs and their price state. Execute re-reads that whole selection while
 * holding a short companion lock, then verifies the state again immediately
 * before every individual native WooCommerce save. The lock serializes only
 * Companion writers: third-party WooCommerce writers do not honor it. For
 * that reason the operation never attempts an automatic rollback. Once a
 * native save has begun, any exception or failed readback is surfaced as a
 * recovery-required result with per-item state.
 */
if ( ! defined( 'ABSPATH' ) ) {
	exit;
}
define( 'WPAGENT_WC_BULK_PRICE_MAX_ITEMS', 50 );
define( 'WPAGENT_WC_BULK_PRICE_LOCK_OPTION', 'wp_agent_wc_bulk_price_lock' );
define( 'WPAGENT_WC_BULK_PRICE_LOCK_TTL', 120 );
function wpagent_perm_wc_bulk_price( $input = null ): bool {
	unset( $input );
	return current_user_can( 'edit_products' );
}

/** @return true|WP_Error */
function wpagent_wc_bulk_price_require_runtime() {
	$woocommerce = wpagent_require_woocommerce();
	if ( is_wp_error( $woocommerce ) ) {
		return $woocommerce;
	}
	if ( ! class_exists( 'WC_Product_Query' ) || ! class_exists( 'WC_Product_Variable' ) || ! function_exists( 'wc_format_decimal' ) || ! function_exists( 'wc_get_price_decimals' ) ) {
		return new WP_Error( 'wpagent_wc_bulk_price_unavailable', __( 'Diese WooCommerce-Version stellt den sicheren Bulk-Preis-Pfad nicht bereit.', 'wp-agent-companion' ), array( 'status' => 409 ) );
	}
	return true;
}

/** @return true|WP_Error */
function wpagent_wc_bulk_price_require_permission() {
	if ( ! current_user_can( 'edit_products' ) ) {
		return new WP_Error( 'wpagent_forbidden', __( 'Du darfst WooCommerce-Produktpreise nicht gesammelt aktualisieren.', 'wp-agent-companion' ), array( 'status' => 403 ) );
	}
	return true;
}

/** @return array<string,mixed> */
function wpagent_wc_bulk_price_selection_schema(): array {
	return array(
		'type'                 => 'object',
		'properties'           => array(
			'taxonomy'           => array( 'type' => 'string', 'enum' => array( 'product_cat', 'product_tag' ) ),
			'term_id'            => array( 'type' => 'integer', 'minimum' => 1 ),
			'include_variations' => array( 'type' => 'boolean' ),
		),
		'required'             => array( 'taxonomy', 'term_id', 'include_variations' ),
		'additionalProperties' => false,
	);
}

/** @return array<string,mixed> */
function wpagent_wc_bulk_price_operation_schema(): array {
	return array(
		'type'                 => 'object',
		'properties'           => array(
			'type'       => array( 'type' => 'string', 'enum' => array( 'percent_discount', 'set_sale_price', 'clear_sale_price' ) ),
			'percent'    => array( 'type' => 'integer', 'minimum' => 1, 'maximum' => 90 ),
			'sale_price' => array( 'type' => 'string', 'minLength' => 1 ),
		),
		'required'             => array( 'type' ),
		'additionalProperties' => false,
	);
}

/** @return array<string,mixed> */
function wpagent_wc_bulk_price_item_schema(): array {
	return array(
		'type'                 => 'object',
		'properties'           => array(
			'product_id'        => array( 'type' => 'integer', 'minimum' => 1 ),
			'object_type'       => array( 'type' => 'string', 'enum' => array( 'product', 'variation' ) ),
			'parent_id'         => array( 'type' => 'integer', 'minimum' => 0 ),
			'product_type'      => array( 'type' => 'string' ),
			'status'            => array( 'type' => 'string' ),
			'regular_price'     => array( 'type' => 'string' ),
			'sale_price'        => array( 'type' => 'string' ),
			'price'             => array( 'type' => 'string' ),
			'sale_from_gmt'     => array( 'type' => 'string' ),
			'sale_to_gmt'       => array( 'type' => 'string' ),
			'price_hash'        => array( 'type' => 'string', 'pattern' => '^[a-f0-9]{64}$' ),
			'next_sale_price'   => array( 'type' => 'string' ),
		),
		'required'             => array( 'product_id', 'object_type', 'parent_id', 'product_type', 'status', 'regular_price', 'sale_price', 'price', 'sale_from_gmt', 'sale_to_gmt', 'price_hash', 'next_sale_price' ),
		'additionalProperties' => false,
	);
}

/** @return array<string,mixed> */
function wpagent_wc_bulk_price_preview_schema(): array {
	return array(
		'type'                 => 'object',
		'properties'           => array(
			'blog_id'               => array( 'type' => 'integer', 'minimum' => 1 ),
			'selection'             => wpagent_wc_bulk_price_selection_schema(),
			'operation'             => wpagent_wc_bulk_price_operation_schema(),
			'store_context'         => array(
				'type'                 => 'object',
				'properties'           => array(
					'woocommerce_version' => array( 'type' => 'string' ),
					'currency'            => array( 'type' => 'string' ),
					'price_decimals'      => array( 'type' => 'integer', 'minimum' => 0 ),
				),
				'required'             => array( 'woocommerce_version', 'currency', 'price_decimals' ),
				'additionalProperties' => false,
			),
			'items'                 => array( 'type' => 'array', 'minItems' => 1, 'maxItems' => WPAGENT_WC_BULK_PRICE_MAX_ITEMS, 'items' => wpagent_wc_bulk_price_item_schema() ),
			'container_product_ids' => array( 'type' => 'array', 'items' => array( 'type' => 'integer', 'minimum' => 1 ) ),
			'collection_hash'       => array( 'type' => 'string', 'pattern' => '^[a-f0-9]{64}$' ),
			'max_items'             => array( 'type' => 'integer', 'minimum' => 1 ),
		),
		'required'             => array( 'blog_id', 'selection', 'operation', 'store_context', 'items', 'container_product_ids', 'collection_hash', 'max_items' ),
		'additionalProperties' => false,
	);
}

/** @return array<string,mixed> */
function wpagent_wc_bulk_price_result_item_schema(): array {
	return array(
		'type'                 => 'object',
		'properties'           => array(
			'product_id' => array( 'type' => 'integer', 'minimum' => 1 ),
			'status'     => array( 'type' => 'string', 'enum' => array( 'updated', 'unchanged', 'recovery_required' ) ),
			'verified'   => array( 'type' => 'boolean' ),
			'item'       => wpagent_wc_bulk_price_item_schema(),
			'error'      => array( 'type' => 'string' ),
		),
		'required'             => array( 'product_id', 'status', 'verified' ),
		'additionalProperties' => false,
	);
}

function wpagent_ability_preview_woocommerce_bulk_price_update(): array {
	return array(
		'label'               => __( 'WooCommerce-Bulk-Preisvorschau erstellen', 'wp-agent-companion' ),
		'description'         => __( 'Erstellt für eine Produktkategorie oder einen Produkttag eine begrenzte, deterministische Preisvorschau mit gebundenem Collection-Hash. Sie ändert keine Preise.', 'wp-agent-companion' ),
		'category'            => 'wp-agent',
		'input_schema'        => array(
			'type'                 => 'object',
			'properties'           => array( 'selection' => wpagent_wc_bulk_price_selection_schema(), 'operation' => wpagent_wc_bulk_price_operation_schema() ),
			'required'             => array( 'selection', 'operation' ),
			'additionalProperties' => false,
		),
		'output_schema'       => wpagent_wc_bulk_price_preview_schema(),
		'execute_callback'    => 'wpagent_exec_preview_woocommerce_bulk_price_update',
		'permission_callback' => 'wpagent_perm_wc_bulk_price',
		'meta'                => array( 'show_in_rest' => true, 'annotations' => array( 'readonly' => true, 'destructive' => false, 'idempotent' => true ) ),
	);
}

function wpagent_ability_execute_woocommerce_bulk_price_update(): array {
	return array(
		'label'               => __( 'Bestätigte WooCommerce-Bulk-Preisvorschau ausführen', 'wp-agent-companion' ),
		'description'         => __( 'Führt ausschließlich eine zuvor geprüfte Preisvorschau mit identischem Hash und identischer ID-Menge aus. Jeder einzelne native Save wird erneut gelesen. Bei Unsicherheit nach einer Mutation ist eine manuelle Wiederherstellung erforderlich.', 'wp-agent-companion' ),
		'category'            => 'wp-agent',
		'input_schema'        => array(
			'type'                 => 'object',
			'properties'           => array(
				'blog_id'                 => array( 'type' => 'integer', 'minimum' => 1 ),
				'selection'               => wpagent_wc_bulk_price_selection_schema(),
				'operation'               => wpagent_wc_bulk_price_operation_schema(),
				'item_ids'                => array( 'type' => 'array', 'minItems' => 1, 'maxItems' => WPAGENT_WC_BULK_PRICE_MAX_ITEMS, 'uniqueItems' => true, 'items' => array( 'type' => 'integer', 'minimum' => 1 ) ),
				'preview_collection_hash' => array( 'type' => 'string', 'pattern' => '^[a-f0-9]{64}$' ),
				'confirm'                 => array( 'type' => 'boolean' ),
			),
			'required'             => array( 'blog_id', 'selection', 'operation', 'item_ids', 'preview_collection_hash', 'confirm' ),
			'additionalProperties' => false,
		),
		'output_schema'       => array(
			'type'                 => 'object',
			'properties'           => array(
				'blog_id'                  => array( 'type' => 'integer', 'minimum' => 1 ),
				'previous_collection_hash' => array( 'type' => 'string', 'pattern' => '^[a-f0-9]{64}$' ),
				'items'                    => array( 'type' => 'array', 'items' => wpagent_wc_bulk_price_result_item_schema() ),
				'updated'                  => array( 'type' => 'integer', 'minimum' => 0 ),
				'unchanged'                => array( 'type' => 'integer', 'minimum' => 0 ),
				'verified'                 => array( 'type' => 'boolean' ),
			),
			'required'             => array( 'blog_id', 'previous_collection_hash', 'items', 'updated', 'unchanged', 'verified' ),
			'additionalProperties' => false,
		),
		'execute_callback'    => 'wpagent_exec_execute_woocommerce_bulk_price_update',
		'permission_callback' => 'wpagent_perm_wc_bulk_price',
		'meta'                => array( 'show_in_rest' => true, 'annotations' => array( 'readonly' => false, 'destructive' => false, 'idempotent' => false ) ),
	);
}

/** @return true|WP_Error */
function wpagent_wc_bulk_price_require_keys( array $input, array $allowed, array $required, string $context ) {
	$unknown = array_diff( array_keys( $input ), $allowed );
	if ( array() !== $unknown ) {
		return new WP_Error( 'wpagent_wc_bulk_price_invalid_input', sprintf( /* translators: %s: context. */ __( 'Unbekannte Felder in %s.', 'wp-agent-companion' ), $context ), array( 'status' => 400 ) );
	}
	foreach ( $required as $field ) {
		if ( ! array_key_exists( $field, $input ) ) {
			return new WP_Error( 'wpagent_wc_bulk_price_invalid_input', sprintf( /* translators: %s: field. */ __( 'Das Feld %s ist erforderlich.', 'wp-agent-companion' ), $field ), array( 'status' => 400 ) );
		}
	}
	return true;
}

/** @return array<string,mixed>|WP_Error */
function wpagent_wc_bulk_price_validate_selection( $raw ) {
	if ( ! is_array( $raw ) ) {
		return new WP_Error( 'wpagent_wc_bulk_price_invalid_selection', __( 'selection muss ein Objekt sein.', 'wp-agent-companion' ), array( 'status' => 400 ) );
	}
	$keys = wpagent_wc_bulk_price_require_keys( $raw, array( 'taxonomy', 'term_id', 'include_variations' ), array( 'taxonomy', 'term_id', 'include_variations' ), 'selection' );
	if ( is_wp_error( $keys ) ) {
		return $keys;
	}
	$taxonomy = (string) $raw['taxonomy'];
	$term_id  = $raw['term_id'];
	if ( ! in_array( $taxonomy, array( 'product_cat', 'product_tag' ), true ) || ! is_int( $term_id ) || $term_id < 1 || ! is_bool( $raw['include_variations'] ) ) {
		return new WP_Error( 'wpagent_wc_bulk_price_invalid_selection', __( 'selection muss eine Produktkategorie oder einen Produkttag, eine positive Term-ID und include_variations als Boolean enthalten.', 'wp-agent-companion' ), array( 'status' => 400 ) );
	}
	$term = get_term( $term_id, $taxonomy );
	if ( ! $term instanceof WP_Term ) {
		return new WP_Error( 'wpagent_wc_bulk_price_term_not_found', __( 'Die gewählte WooCommerce-Produkttaxonomie existiert nicht.', 'wp-agent-companion' ), array( 'status' => 404 ) );
	}
	return array( 'taxonomy' => $taxonomy, 'term_id' => (int) $term->term_id, 'include_variations' => $raw['include_variations'] );
}

/** @return array<string,mixed>|WP_Error */
function wpagent_wc_bulk_price_validate_operation( $raw ) {
	if ( ! is_array( $raw ) ) {
		return new WP_Error( 'wpagent_wc_bulk_price_invalid_operation', __( 'operation muss ein Objekt sein.', 'wp-agent-companion' ), array( 'status' => 400 ) );
	}
	$keys = wpagent_wc_bulk_price_require_keys( $raw, array( 'type', 'percent', 'sale_price' ), array( 'type' ), 'operation' );
	if ( is_wp_error( $keys ) ) {
		return $keys;
	}
	$type = (string) $raw['type'];
	if ( ! in_array( $type, array( 'percent_discount', 'set_sale_price', 'clear_sale_price' ), true ) ) {
		return new WP_Error( 'wpagent_wc_bulk_price_invalid_operation', __( 'Der Preisvorgang ist nicht zulässig.', 'wp-agent-companion' ), array( 'status' => 400 ) );
	}
	if ( 'percent_discount' === $type ) {
		if ( 2 !== count( $raw ) || ! array_key_exists( 'percent', $raw ) || ! is_int( $raw['percent'] ) || $raw['percent'] < 1 || $raw['percent'] > 90 ) {
			return new WP_Error( 'wpagent_wc_bulk_price_invalid_operation', __( 'Ein prozentualer Rabatt benötigt percent als ganze Zahl von 1 bis 90.', 'wp-agent-companion' ), array( 'status' => 400 ) );
		}
		return array( 'type' => $type, 'percent' => $raw['percent'] );
	}
	if ( 'set_sale_price' === $type ) {
		if ( 2 !== count( $raw ) || ! array_key_exists( 'sale_price', $raw ) || ! is_string( $raw['sale_price'] ) ) {
			return new WP_Error( 'wpagent_wc_bulk_price_invalid_operation', __( 'Ein fester Angebotspreis benötigt ausschließlich sale_price.', 'wp-agent-companion' ), array( 'status' => 400 ) );
		}
		$price = wpagent_sanitize_wc_price( $raw['sale_price'], 'sale_price' );
		if ( is_wp_error( $price ) || '' === $price || (float) $price <= 0 ) {
			return new WP_Error( 'wpagent_wc_bulk_price_invalid_operation', __( 'sale_price muss eine positive Zahl sein.', 'wp-agent-companion' ), array( 'status' => 400 ) );
		}
		return array( 'type' => $type, 'sale_price' => wc_format_decimal( $price, wc_get_price_decimals() ) );
	}
	if ( 1 !== count( $raw ) ) {
		return new WP_Error( 'wpagent_wc_bulk_price_invalid_operation', __( 'clear_sale_price akzeptiert keine weiteren Felder.', 'wp-agent-companion' ), array( 'status' => 400 ) );
	}
	return array( 'type' => $type );
}

/** @return array<string,mixed>|WP_Error */
function wpagent_wc_bulk_price_validate_preview_input( $input ) {
	if ( ! is_array( $input ) ) {
		return new WP_Error( 'wpagent_wc_bulk_price_invalid_input', __( 'Der Input muss ein Objekt sein.', 'wp-agent-companion' ), array( 'status' => 400 ) );
	}
	$keys = wpagent_wc_bulk_price_require_keys( $input, array( 'selection', 'operation' ), array( 'selection', 'operation' ), 'Input' );
	if ( is_wp_error( $keys ) ) {
		return $keys;
	}
	$selection = wpagent_wc_bulk_price_validate_selection( $input['selection'] );
	if ( is_wp_error( $selection ) ) {
		return $selection;
	}
	$operation = wpagent_wc_bulk_price_validate_operation( $input['operation'] );
	if ( is_wp_error( $operation ) ) {
		return $operation;
	}
	return array( 'selection' => $selection, 'operation' => $operation );
}

/** @return array<string,mixed>|WP_Error */
function wpagent_wc_bulk_price_validate_execute_input( $input ) {
	if ( ! is_array( $input ) ) {
		return new WP_Error( 'wpagent_wc_bulk_price_invalid_input', __( 'Der Input muss ein Objekt sein.', 'wp-agent-companion' ), array( 'status' => 400 ) );
	}
	$keys = wpagent_wc_bulk_price_require_keys( $input, array( 'blog_id', 'selection', 'operation', 'item_ids', 'preview_collection_hash', 'confirm' ), array( 'blog_id', 'selection', 'operation', 'item_ids', 'preview_collection_hash', 'confirm' ), 'Input' );
	if ( is_wp_error( $keys ) ) {
		return $keys;
	}
	if ( ! is_int( $input['blog_id'] ) || $input['blog_id'] < 1 || ! is_array( $input['item_ids'] ) || ! is_bool( $input['confirm'] ) || ! is_string( $input['preview_collection_hash'] ) || 1 !== preg_match( '/^[a-f0-9]{64}$/', $input['preview_collection_hash'] ) ) {
		return new WP_Error( 'wpagent_wc_bulk_price_invalid_input', __( 'blog_id, item_ids, preview_collection_hash oder confirm sind ungültig.', 'wp-agent-companion' ), array( 'status' => 400 ) );
	}
	$ids = array_values( $input['item_ids'] );
	if ( array() === $ids || count( $ids ) > WPAGENT_WC_BULK_PRICE_MAX_ITEMS ) {
		return new WP_Error( 'wpagent_wc_bulk_price_invalid_input', __( 'item_ids muss eine nicht leere, begrenzte Liste sein.', 'wp-agent-companion' ), array( 'status' => 400 ) );
	}
	$previous = 0;
	foreach ( $ids as $id ) {
		if ( ! is_int( $id ) || $id < 1 || $id <= $previous ) {
			return new WP_Error( 'wpagent_wc_bulk_price_invalid_input', __( 'item_ids muss eindeutige, aufsteigend sortierte positive IDs enthalten.', 'wp-agent-companion' ), array( 'status' => 400 ) );
		}
		$previous = $id;
	}
	$preview = wpagent_wc_bulk_price_validate_preview_input( array( 'selection' => $input['selection'], 'operation' => $input['operation'] ) );
	if ( is_wp_error( $preview ) ) {
		return $preview;
	}
	return $preview + array(
		'blog_id'                 => $input['blog_id'],
		'item_ids'                => $ids,
		'preview_collection_hash' => $input['preview_collection_hash'],
		'confirm'                 => $input['confirm'],
	);
}

/** @return array<string,mixed> */
function wpagent_wc_bulk_price_item( WC_Product $product ): array {
	$is_variation = $product instanceof WC_Product_Variation;
	$sale_from    = $product->get_date_on_sale_from();
	$sale_to      = $product->get_date_on_sale_to();
	$item         = array(
		'product_id'    => (int) $product->get_id(),
		'object_type'   => $is_variation ? 'variation' : 'product',
		'parent_id'     => $is_variation ? (int) $product->get_parent_id() : 0,
		'product_type'  => (string) $product->get_type(),
		'status'        => (string) $product->get_status(),
		'regular_price' => (string) $product->get_regular_price(),
		'sale_price'    => (string) $product->get_sale_price(),
		'price'         => (string) $product->get_price(),
		'sale_from_gmt' => $sale_from instanceof WC_DateTime ? $sale_from->date( 'Y-m-d H:i:s' ) : '',
		'sale_to_gmt'   => $sale_to instanceof WC_DateTime ? $sale_to->date( 'Y-m-d H:i:s' ) : '',
	);
	$item['price_hash'] = hash( 'sha256', (string) wp_json_encode( $item, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE ) );
	return $item;
}

/** @return array<string,mixed> */
function wpagent_wc_bulk_price_store_context(): array {
	return array(
		'woocommerce_version' => defined( 'WC_VERSION' ) ? (string) WC_VERSION : '',
		'currency'            => function_exists( 'get_woocommerce_currency' ) ? (string) get_woocommerce_currency() : '',
		'price_decimals'      => (int) wc_get_price_decimals(),
	);
}

/** @return string|WP_Error */
function wpagent_wc_bulk_price_next_sale_price( array $item, array $operation ) {
	if ( '' !== $item['sale_from_gmt'] || '' !== $item['sale_to_gmt'] ) {
		return new WP_Error( 'wpagent_wc_bulk_price_scheduled_sale', sprintf( /* translators: %d: product ID. */ __( 'Produkt oder Variante #%d hat geplante Angebotsdaten und wird aus Sicherheitsgründen nicht gesammelt bepreist.', 'wp-agent-companion' ), (int) $item['product_id'] ), array( 'status' => 409 ) );
	}
	if ( 'clear_sale_price' === $operation['type'] ) {
		return '';
	}
	$regular = (string) $item['regular_price'];
	if ( '' === $regular || ! is_numeric( $regular ) || (float) $regular <= 0 ) {
		return new WP_Error( 'wpagent_wc_bulk_price_unpriceable', sprintf( /* translators: %d: product ID. */ __( 'Produkt oder Variante #%d hat keinen positiven regulären Preis.', 'wp-agent-companion' ), (int) $item['product_id'] ), array( 'status' => 400 ) );
	}
	if ( 'percent_discount' === $operation['type'] ) {
		$next = wc_format_decimal( ( (float) $regular * ( 100 - (int) $operation['percent'] ) ) / 100, wc_get_price_decimals() );
	} else {
		$next = (string) $operation['sale_price'];
	}
	if ( '' === $next || ! is_numeric( $next ) || (float) $next <= 0 || (float) $next >= (float) $regular ) {
		return new WP_Error( 'wpagent_wc_bulk_price_invalid_sale_price', sprintf( /* translators: %d: product ID. */ __( 'Der berechnete Angebotspreis für Produkt oder Variante #%d muss positiv und kleiner als der reguläre Preis sein.', 'wp-agent-companion' ), (int) $item['product_id'] ), array( 'status' => 400 ) );
	}
	return $next;
}

/** @return true|WP_Error */
function wpagent_wc_bulk_price_require_editable( WC_Product $product ): bool|WP_Error {
	if ( ! current_user_can( 'edit_post', $product->get_id() ) ) {
		return new WP_Error( 'wpagent_forbidden', __( 'Du darfst mindestens eines der gewählten Produkte oder Varianten nicht bearbeiten.', 'wp-agent-companion' ), array( 'status' => 403 ) );
	}
	if ( $product instanceof WC_Product_Variation && ! current_user_can( 'edit_post', $product->get_parent_id() ) ) {
		return new WP_Error( 'wpagent_forbidden', __( 'Du darfst das Elternprodukt mindestens einer gewählten Variante nicht bearbeiten.', 'wp-agent-companion' ), array( 'status' => 403 ) );
	}
	return true;
}

/** @return array<string,mixed>|WP_Error */
function wpagent_wc_bulk_price_build_preview( array $selection, array $operation ) {
	$term = get_term( (int) $selection['term_id'], (string) $selection['taxonomy'] );
	if ( ! $term instanceof WP_Term ) {
		return new WP_Error( 'wpagent_wc_bulk_price_term_not_found', __( 'Die gewählte WooCommerce-Produkttaxonomie existiert nicht mehr.', 'wp-agent-companion' ), array( 'status' => 409 ) );
	}
	$query_args = array(
		'limit'    => WPAGENT_WC_BULK_PRICE_MAX_ITEMS + 1,
		'paginate' => false,
		// Fetch IDs first. The hard selection limit must be enforced before
		// WooCommerce instantiates potentially expensive product objects.
		'return'   => 'ids',
		'status'   => array( 'publish', 'draft', 'pending', 'private' ),
		'orderby'  => 'ID',
		'order'    => 'ASC',
	);
	$query_args[ 'product_cat' === $selection['taxonomy'] ? 'category' : 'tag' ] = array( (string) $term->slug );
	$product_ids = ( new WC_Product_Query( $query_args ) )->get_products();
	if ( ! is_array( $product_ids ) ) {
		return new WP_Error( 'wpagent_wc_bulk_price_query_failed', __( 'WooCommerce konnte die gewählte Produktmenge nicht sicher lesen.', 'wp-agent-companion' ), array( 'status' => 502 ) );
	}
	if ( count( $product_ids ) > WPAGENT_WC_BULK_PRICE_MAX_ITEMS ) {
		return new WP_Error( 'wpagent_wc_bulk_price_limit', sprintf( /* translators: %d: limit. */ __( 'Die Auswahl enthält mehr als %d Produkte. Verfeinere Kategorie oder Tag vor einem Bulk-Preisvorgang.', 'wp-agent-companion' ), WPAGENT_WC_BULK_PRICE_MAX_ITEMS ), array( 'status' => 413 ) );
	}
	$product_ids = array_values( array_map( 'intval', $product_ids ) );
	if ( count( $product_ids ) !== count( array_unique( $product_ids ) ) || in_array( 0, $product_ids, true ) ) {
		return new WP_Error( 'wpagent_wc_bulk_price_provider_state_invalid', __( 'WooCommerce lieferte eine ungültige Produkt-ID-Auswahl.', 'wp-agent-companion' ), array( 'status' => 502 ) );
	}
	sort( $product_ids, SORT_NUMERIC );
	$items      = array();
	$containers = array();
	foreach ( $product_ids as $product_id ) {
		$product = wc_get_product( $product_id );
		if ( ! $product instanceof WC_Product ) {
			return new WP_Error( 'wpagent_wc_bulk_price_provider_state_invalid', __( 'WooCommerce lieferte ein ungültiges Produktobjekt.', 'wp-agent-companion' ), array( 'status' => 502 ) );
		}
		$editable = wpagent_wc_bulk_price_require_editable( $product );
		if ( is_wp_error( $editable ) ) {
			return $editable;
		}
		if ( $product instanceof WC_Product_Variable ) {
			$containers[] = (int) $product->get_id();
			if ( ! $selection['include_variations'] ) {
				continue;
			}
			$remaining = WPAGENT_WC_BULK_PRICE_MAX_ITEMS - count( $items );
			if ( $remaining < 1 ) {
				return new WP_Error( 'wpagent_wc_bulk_price_limit', sprintf( /* translators: %d: limit. */ __( 'Die Auswahl einschließlich Varianten enthält mehr als %d Preisziele. Verfeinere Kategorie oder Tag vor einem Bulk-Preisvorgang.', 'wp-agent-companion' ), WPAGENT_WC_BULK_PRICE_MAX_ITEMS ), array( 'status' => 413 ) );
			}
			global $wpdb;
			$wpdb->last_error = '';
			$child_rows       = $wpdb->get_col(
				$wpdb->prepare(
					"SELECT ID FROM {$wpdb->posts} WHERE post_parent = %d AND post_type = 'product_variation' AND post_status IN ('publish','private') ORDER BY menu_order ASC, ID ASC LIMIT %d",
					(int) $product->get_id(),
					$remaining + 1
				)
			);
			if ( ! is_array( $child_rows ) || '' !== $wpdb->last_error ) {
				return new WP_Error( 'wpagent_wc_bulk_price_query_failed', __( 'WooCommerce konnte die Produktvarianten nicht sicher lesen.', 'wp-agent-companion' ), array( 'status' => 502 ) );
			}
			$children = array_map(
				'intval',
				$child_rows
			);
			if ( count( $children ) > $remaining ) {
				return new WP_Error( 'wpagent_wc_bulk_price_limit', sprintf( /* translators: %d: limit. */ __( 'Die Auswahl einschließlich Varianten enthält mehr als %d Preisziele. Verfeinere Kategorie oder Tag vor einem Bulk-Preisvorgang.', 'wp-agent-companion' ), WPAGENT_WC_BULK_PRICE_MAX_ITEMS ), array( 'status' => 413 ) );
			}
			sort( $children, SORT_NUMERIC );
			foreach ( $children as $child_id ) {
				$variation = wc_get_product( $child_id );
				if ( ! $variation instanceof WC_Product_Variation || (int) $variation->get_parent_id() !== (int) $product->get_id() ) {
					return new WP_Error( 'wpagent_wc_bulk_price_provider_state_invalid', __( 'WooCommerce lieferte eine ungültige Produktvariante.', 'wp-agent-companion' ), array( 'status' => 502 ) );
				}
				$editable = wpagent_wc_bulk_price_require_editable( $variation );
				if ( is_wp_error( $editable ) ) {
					return $editable;
				}
				$items[] = wpagent_wc_bulk_price_item( $variation );
			}
			continue;
		}
		if ( ! $product->is_type( 'simple' ) ) {
			return new WP_Error( 'wpagent_wc_bulk_price_unsupported_product_type', sprintf( /* translators: %d: product ID. */ __( 'Produkt #%d ist kein sicher unterstütztes einfaches oder variables WooCommerce-Produkt.', 'wp-agent-companion' ), (int) $product->get_id() ), array( 'status' => 400 ) );
		}
		$items[] = wpagent_wc_bulk_price_item( $product );
	}
	usort( $items, static fn( array $a, array $b ): int => $a['product_id'] <=> $b['product_id'] );
	if ( array() === $items ) {
		return new WP_Error( 'wpagent_wc_bulk_price_empty_selection', __( 'Die Auswahl enthält keine eigenständig bepreisbaren Produkte oder Varianten.', 'wp-agent-companion' ), array( 'status' => 400 ) );
	}
	if ( count( $items ) > WPAGENT_WC_BULK_PRICE_MAX_ITEMS ) {
		return new WP_Error( 'wpagent_wc_bulk_price_limit', sprintf( /* translators: %d: limit. */ __( 'Die Auswahl einschließlich Varianten enthält mehr als %d Preisziele. Verfeinere Kategorie oder Tag vor einem Bulk-Preisvorgang.', 'wp-agent-companion' ), WPAGENT_WC_BULK_PRICE_MAX_ITEMS ), array( 'status' => 413 ) );
	}
	foreach ( $items as $index => $item ) {
		$next = wpagent_wc_bulk_price_next_sale_price( $item, $operation );
		if ( is_wp_error( $next ) ) {
			return $next;
		}
		$items[ $index ]['next_sale_price'] = $next;
	}
	sort( $containers, SORT_NUMERIC );
	$preview = array(
		'blog_id'               => (int) get_current_blog_id(),
		'selection'             => $selection,
		'operation'             => $operation,
		'store_context'         => wpagent_wc_bulk_price_store_context(),
		'items'                 => $items,
		'container_product_ids' => array_values( array_unique( $containers ) ),
		'max_items'             => WPAGENT_WC_BULK_PRICE_MAX_ITEMS,
	);
	$preview['collection_hash'] = hash( 'sha256', (string) wp_json_encode( $preview, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE ) );
	return $preview;
}

/** @return string|WP_Error */
function wpagent_wc_bulk_price_acquire_lock() {
	$token = wpagent_companion_acquire_option_lock( WPAGENT_WC_BULK_PRICE_LOCK_OPTION, WPAGENT_WC_BULK_PRICE_LOCK_TTL );
	if ( is_string( $token ) ) {
		return $token;
	}
	return new WP_Error( 'wpagent_wc_bulk_price_locked', __( 'Ein anderer WooCommerce-Bulk-Preisvorgang läuft bereits.', 'wp-agent-companion' ), array( 'status' => 409 ) );
}

function wpagent_wc_bulk_price_release_lock( string $token ): void {
	wpagent_companion_release_option_lock( WPAGENT_WC_BULK_PRICE_LOCK_OPTION, $token );
}

function wpagent_wc_bulk_price_lock_is_owned( string $token ): bool {
	return wpagent_companion_option_lock_is_owned( WPAGENT_WC_BULK_PRICE_LOCK_OPTION, $token );
}

function wpagent_wc_bulk_price_refresh_lock( string $token ): bool {
	return wpagent_companion_refresh_option_lock(
		WPAGENT_WC_BULK_PRICE_LOCK_OPTION,
		$token,
		WPAGENT_WC_BULK_PRICE_LOCK_TTL
	);
}

/**
 * Renews the short-lived lock and proves that it is still owned afterwards.
 *
 * Renewal by itself is not a sufficient proof because another writer can
 * replace the option between the update and the next product operation.
 */
function wpagent_wc_bulk_price_checkpoint_lock( string $token ): bool {
	return wpagent_wc_bulk_price_refresh_lock( $token )
		&& wpagent_wc_bulk_price_lock_is_owned( $token );
}

/** @return true|WP_Error */
function wpagent_wc_bulk_price_item_still_selected( array $item, array $selection ) {
	$selection_id = 'variation' === $item['object_type'] ? (int) $item['parent_id'] : (int) $item['product_id'];
	if ( $selection_id < 1 || ! has_term( (int) $selection['term_id'], (string) $selection['taxonomy'], $selection_id ) ) {
		return new WP_Error( 'wpagent_wc_bulk_price_selection_drift', __( 'Ein Preisziel gehört nicht mehr zur bestätigten Kategorie oder zum bestätigten Tag.', 'wp-agent-companion' ), array( 'status' => 409 ) );
	}
	return true;
}

/** @return array<string,mixed>|WP_Error */
function wpagent_wc_bulk_price_current_item( array $expected, array $selection ) {
	$product = wc_get_product( (int) $expected['product_id'] );
	if ( ! $product instanceof WC_Product ) {
		return new WP_Error( 'wpagent_wc_bulk_price_item_missing', __( 'Ein Preisziel existiert nicht mehr.', 'wp-agent-companion' ), array( 'status' => 409 ) );
	}
	$editable = wpagent_wc_bulk_price_require_editable( $product );
	if ( is_wp_error( $editable ) ) {
		return $editable;
	}
	$current = wpagent_wc_bulk_price_item( $product );
	foreach ( array( 'product_id', 'object_type', 'parent_id', 'product_type', 'status', 'regular_price', 'sale_price', 'price', 'sale_from_gmt', 'sale_to_gmt', 'price_hash' ) as $field ) {
		if ( ! array_key_exists( $field, $expected ) || ! hash_equals( (string) $expected[ $field ], (string) $current[ $field ] ) ) {
			return new WP_Error( 'wpagent_wc_bulk_price_item_drift', __( 'Ein Preisziel hat sich seit der Vorschau geändert.', 'wp-agent-companion' ), array( 'status' => 409 ) );
		}
	}
	$selected = wpagent_wc_bulk_price_item_still_selected( $current, $selection );
	if ( is_wp_error( $selected ) ) {
		return $selected;
	}
	return array( 'product' => $product, 'item' => $current );
}

/** @return 'verified'|'readback_mismatch'|'readback_missing'|'permission_lost'|'lock_lost' */
function wpagent_wc_bulk_price_write_verified( WC_Product $product, array $expected, string $next_sale_price, string $lock ): string {
	if ( ! wpagent_wc_bulk_price_checkpoint_lock( $lock ) ) {
		return 'lock_lost';
	}
	$fresh = wc_get_product( $product->get_id() );
	if ( ! wpagent_wc_bulk_price_checkpoint_lock( $lock ) ) {
		return 'lock_lost';
	}
	if ( ! $fresh instanceof WC_Product ) {
		return 'readback_missing';
	}
	if ( is_wp_error( wpagent_wc_bulk_price_require_editable( $fresh ) ) ) {
		return 'permission_lost';
	}
	$state = wpagent_wc_bulk_price_item( $fresh );
	$expected_price = '' === $next_sale_price ? (string) $expected['regular_price'] : $next_sale_price;
	$verified = $state['object_type'] === $expected['object_type']
		&& $state['parent_id'] === $expected['parent_id']
		&& $state['product_type'] === $expected['product_type']
		&& $state['status'] === $expected['status']
		&& $state['regular_price'] === $expected['regular_price']
		&& $state['sale_price'] === $next_sale_price
		&& $state['price'] === $expected_price
		&& '' === $state['sale_from_gmt']
		&& '' === $state['sale_to_gmt'];
	if ( ! $verified || ! $fresh instanceof WC_Product_Variation ) {
		return $verified ? 'verified' : 'readback_mismatch';
	}
	if ( ! wpagent_wc_bulk_price_checkpoint_lock( $lock ) ) {
		return 'lock_lost';
	}
	$parent = wc_get_product( $fresh->get_parent_id() );
	if ( ! wpagent_wc_bulk_price_checkpoint_lock( $lock ) ) {
		return 'lock_lost';
	}
	if ( ! $parent instanceof WC_Product_Variable ) {
		return 'readback_mismatch';
	}
	$prices = $parent->get_variation_prices( false );
	if ( ! wpagent_wc_bulk_price_checkpoint_lock( $lock ) ) {
		return 'lock_lost';
	}
	$range_price = $prices['price'][ $fresh->get_id() ] ?? null;
	return is_scalar( $range_price ) && (string) $range_price === $expected_price ? 'verified' : 'readback_mismatch';
}

/** @return WP_Error */
function wpagent_wc_bulk_price_recovery_error( array $items, string $reason ): WP_Error {
	return new WP_Error(
		'wpagent_wc_bulk_price_recovery_required',
		__( 'Der Bulk-Preisvorgang hat nach einer begonnenen Mutation einen unsicheren Zustand erkannt. Es wurde kein automatischer Rollback versucht. Prüfe die aufgeführten Produkte oder Varianten manuell.', 'wp-agent-companion' ),
		array( 'status' => 409, 'recovery_required' => true, 'reason' => $reason, 'items' => $items )
	);
}

/** @param array<int,array<string,mixed>> $results */
function wpagent_wc_bulk_price_lock_lost_error( array &$results, int $product_id, bool $mutation_started ): WP_Error {
	if ( $mutation_started ) {
		if ( $product_id > 0 ) {
			$results[] = array( 'product_id' => $product_id, 'status' => 'recovery_required', 'verified' => false, 'error' => 'lock_lost' );
		}
		return wpagent_wc_bulk_price_recovery_error( $results, 'lock_lost' );
	}
	return new WP_Error( 'wpagent_wc_bulk_price_lock_lost', __( 'Der Bulk-Preisvorgang hat seine exklusive Companion-Sperre verloren.', 'wp-agent-companion' ), array( 'status' => 409 ) );
}

/** @return array<string,mixed>|WP_Error */
function wpagent_exec_preview_woocommerce_bulk_price_update( $input = array() ) {
	$permitted = wpagent_wc_bulk_price_require_permission();
	if ( is_wp_error( $permitted ) ) {
		return $permitted;
	}
	$available = wpagent_wc_bulk_price_require_runtime();
	if ( is_wp_error( $available ) ) {
		return $available;
	}
	$validated = wpagent_wc_bulk_price_validate_preview_input( $input );
	if ( is_wp_error( $validated ) ) {
		return $validated;
	}
	return wpagent_wc_bulk_price_build_preview( $validated['selection'], $validated['operation'] );
}

/** @return array<string,mixed>|WP_Error */
function wpagent_exec_execute_woocommerce_bulk_price_update( $input = array() ) {
	$permitted = wpagent_wc_bulk_price_require_permission();
	if ( is_wp_error( $permitted ) ) {
		return $permitted;
	}
	$available = wpagent_wc_bulk_price_require_runtime();
	if ( is_wp_error( $available ) ) {
		return $available;
	}
	$validated = wpagent_wc_bulk_price_validate_execute_input( $input );
	if ( is_wp_error( $validated ) ) {
		return $validated;
	}
	if ( true !== $validated['confirm'] ) {
		return new WP_Error( 'wpagent_confirmation_required', __( 'Zum Ausführen des Bulk-Preisvorgangs ist confirm=true erforderlich.', 'wp-agent-companion' ), array( 'status' => 400 ) );
	}
	if ( (int) get_current_blog_id() !== $validated['blog_id'] ) {
		return new WP_Error( 'wpagent_wc_bulk_price_blog_mismatch', __( 'Die Vorschau gehört zu einer anderen WordPress-Subsite.', 'wp-agent-companion' ), array( 'status' => 409 ) );
	}
	$lock = wpagent_wc_bulk_price_acquire_lock();
	if ( is_wp_error( $lock ) ) {
		return $lock;
	}
	try {
		$results           = array();
		$updated           = 0;
		$unchanged         = 0;
		$mutation_started  = false;
		if ( ! wpagent_wc_bulk_price_checkpoint_lock( $lock ) ) {
			return wpagent_wc_bulk_price_lock_lost_error( $results, 0, $mutation_started );
		}
		$preview = wpagent_wc_bulk_price_build_preview( $validated['selection'], $validated['operation'] );
		if ( is_wp_error( $preview ) ) {
			return $preview;
		}
		if ( ! wpagent_wc_bulk_price_checkpoint_lock( $lock ) ) {
			return wpagent_wc_bulk_price_lock_lost_error( $results, 0, $mutation_started );
		}
		$preview_ids = array_map( static fn( array $item ): int => (int) $item['product_id'], $preview['items'] );
		if ( ! hash_equals( $validated['preview_collection_hash'], $preview['collection_hash'] ) || $validated['item_ids'] !== $preview_ids ) {
			return new WP_Error( 'wpagent_wc_bulk_price_preview_stale', __( 'Kategorie, Tag, Preiszustand oder ID-Menge hat sich seit der Vorschau geändert. Erstelle eine neue Vorschau.', 'wp-agent-companion' ), array( 'status' => 409 ) );
		}

		foreach ( $preview['items'] as $item ) {
			$product_id = (int) $item['product_id'];
			if ( ! wpagent_wc_bulk_price_checkpoint_lock( $lock ) ) {
				return wpagent_wc_bulk_price_lock_lost_error( $results, $product_id, $mutation_started );
			}
			$current = wpagent_wc_bulk_price_current_item( $item, $validated['selection'] );
			if ( is_wp_error( $current ) ) {
				if ( $mutation_started ) {
					$results[] = array( 'product_id' => $product_id, 'status' => 'recovery_required', 'verified' => false, 'error' => $current->get_error_code() );
					return wpagent_wc_bulk_price_recovery_error( $results, $current->get_error_code() );
				}
				return $current;
			}
			if ( ! wpagent_wc_bulk_price_checkpoint_lock( $lock ) ) {
				return wpagent_wc_bulk_price_lock_lost_error( $results, $product_id, $mutation_started );
			}
			$product = $current['product'];
			if ( (string) $item['sale_price'] === (string) $item['next_sale_price'] ) {
				if ( ! wpagent_wc_bulk_price_checkpoint_lock( $lock ) ) {
					return wpagent_wc_bulk_price_lock_lost_error( $results, $product_id, $mutation_started );
				}
				$results[] = array( 'product_id' => $product_id, 'status' => 'unchanged', 'verified' => true, 'item' => $item );
				++$unchanged;
				continue;
			}
			if ( ! wpagent_wc_bulk_price_checkpoint_lock( $lock ) ) {
				return wpagent_wc_bulk_price_lock_lost_error( $results, $product_id, $mutation_started );
			}
			try {
				$product->set_sale_price( (string) $item['next_sale_price'] );
			} catch ( Throwable $error ) {
				return new WP_Error( 'wpagent_wc_bulk_price_prepare_failed', __( 'WooCommerce konnte den neuen Angebotspreis nicht zur Speicherung vorbereiten.', 'wp-agent-companion' ), array( 'status' => 502 ) );
			}
			if ( ! wpagent_wc_bulk_price_checkpoint_lock( $lock ) ) {
				return wpagent_wc_bulk_price_lock_lost_error( $results, $product_id, $mutation_started );
			}
			$mutation_started = true;
			try {
				$product->save();
			} catch ( Throwable $error ) {
				$results[] = array( 'product_id' => $product_id, 'status' => 'recovery_required', 'verified' => false, 'error' => 'native_save_failed' );
				return wpagent_wc_bulk_price_recovery_error( $results, 'native_save_failed' );
			}
			if ( ! wpagent_wc_bulk_price_checkpoint_lock( $lock ) ) {
				return wpagent_wc_bulk_price_lock_lost_error( $results, $product_id, $mutation_started );
			}
			try {
				if ( $product instanceof WC_Product_Variation ) {
					WC_Product_Variable::sync( $product->get_parent_id() );
					wc_delete_product_transients( $product->get_parent_id() );
				} else {
					wc_delete_product_transients( $product->get_id() );
				}
			} catch ( Throwable $error ) {
				$results[] = array( 'product_id' => $product_id, 'status' => 'recovery_required', 'verified' => false, 'error' => 'native_sync_failed' );
				return wpagent_wc_bulk_price_recovery_error( $results, 'native_sync_failed' );
			}
			if ( ! wpagent_wc_bulk_price_checkpoint_lock( $lock ) ) {
				return wpagent_wc_bulk_price_lock_lost_error( $results, $product_id, $mutation_started );
			}
			$write_verification = wpagent_wc_bulk_price_write_verified( $product, $item, (string) $item['next_sale_price'], $lock );
			if ( 'verified' !== $write_verification ) {
				$results[] = array( 'product_id' => $product_id, 'status' => 'recovery_required', 'verified' => false, 'error' => $write_verification );
				return wpagent_wc_bulk_price_recovery_error( $results, $write_verification );
			}
			if ( ! wpagent_wc_bulk_price_checkpoint_lock( $lock ) ) {
				return wpagent_wc_bulk_price_lock_lost_error( $results, $product_id, $mutation_started );
			}
			if ( ! wpagent_wc_bulk_price_checkpoint_lock( $lock ) ) {
				return wpagent_wc_bulk_price_lock_lost_error( $results, $product_id, $mutation_started );
			}
			$fresh_product = wc_get_product( $product->get_id() );
			if ( ! wpagent_wc_bulk_price_checkpoint_lock( $lock ) ) {
				return wpagent_wc_bulk_price_lock_lost_error( $results, $product_id, $mutation_started );
			}
			if ( ! $fresh_product instanceof WC_Product ) {
				$results[] = array( 'product_id' => $product_id, 'status' => 'recovery_required', 'verified' => false, 'error' => 'readback_missing' );
				return wpagent_wc_bulk_price_recovery_error( $results, 'readback_missing' );
			}
			if ( is_wp_error( wpagent_wc_bulk_price_require_editable( $fresh_product ) ) ) {
				$results[] = array( 'product_id' => $product_id, 'status' => 'recovery_required', 'verified' => false, 'error' => 'permission_lost' );
				return wpagent_wc_bulk_price_recovery_error( $results, 'permission_lost' );
			}
			$fresh_item = wpagent_wc_bulk_price_item( $fresh_product );
			if ( ! wpagent_wc_bulk_price_checkpoint_lock( $lock ) ) {
				return wpagent_wc_bulk_price_lock_lost_error( $results, $product_id, $mutation_started );
			}
			$fresh_item['next_sale_price'] = (string) $item['next_sale_price'];
			$results[] = array( 'product_id' => $product_id, 'status' => 'updated', 'verified' => true, 'item' => $fresh_item );
			++$updated;
		}
		if ( ! wpagent_wc_bulk_price_checkpoint_lock( $lock ) ) {
			return wpagent_wc_bulk_price_lock_lost_error( $results, 0, $mutation_started );
		}
		return array(
			'blog_id'                  => (int) get_current_blog_id(),
			'previous_collection_hash' => $preview['collection_hash'],
			'items'                    => $results,
			'updated'                  => $updated,
			'unchanged'                => $unchanged,
			'verified'                 => true,
		);
	} finally {
		wpagent_wc_bulk_price_release_lock( $lock );
	}
}
