<?php
/** Shared WooCommerce order helpers, privacy gates, and schemas. */

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

define( 'WPAGENT_COMPANION_OPT_WC_ORDER_GRANTS', 'wp_agent_companion_wc_order_capability_grants' );
define( 'WPAGENT_WC_ORDER_LOCK_PREFIX', 'wp_agent_wc_order_lock_' );
define( 'WPAGENT_WC_ORDER_LOCK_TTL', 30 );
define( 'WPAGENT_WC_ORDER_MAX_RESPONSE_ITEMS', 100 );
define( 'WPAGENT_WC_ORDER_MAX_RESPONSE_TEXT_BYTES', 16384 );
define( 'WPAGENT_WC_ORDER_MAX_NOTE_CHARACTERS', 2000 );
define( 'WPAGENT_WC_ORDER_MAX_NOTE_BYTES', 8000 );
define( 'WPAGENT_WC_ORDER_MAX_RESPONSE_INTEGER', 2147483647 );

/** @return string */
function wpagent_wc_order_lock_option( int $order_id ): string {
	return WPAGENT_WC_ORDER_LOCK_PREFIX . $order_id;
}

/** @return string|WP_Error */
function wpagent_wc_acquire_order_lock( int $order_id ) {
	$token = $order_id > 0
		? wpagent_companion_acquire_option_lock( wpagent_wc_order_lock_option( $order_id ), WPAGENT_WC_ORDER_LOCK_TTL )
		: false;
	if ( is_string( $token ) && '' !== $token ) {
		return $token;
	}
	return new WP_Error( 'wpagent_wc_order_locked', __( 'Diese WooCommerce-Bestellung wird gerade durch einen anderen Companion-Vorgang bearbeitet.', 'wp-agent-companion' ), array( 'status' => 409 ) );
}

function wpagent_wc_release_order_lock( int $order_id, string $token ): void {
	wpagent_companion_release_option_lock( wpagent_wc_order_lock_option( $order_id ), $token );
}

function wpagent_wc_order_lock_checkpoint( int $order_id, string $token ): bool {
	$option = wpagent_wc_order_lock_option( $order_id );
	return wpagent_companion_refresh_option_lock( $option, $token, WPAGENT_WC_ORDER_LOCK_TTL )
		&& wpagent_companion_option_lock_is_owned( $option, $token );
}

/** @return WC_Order|false|WP_Error */
function wpagent_wc_fresh_order( int $order_id ) {
	if ( $order_id <= 0 ) {
		return false;
	}
	$order_cache_class = '\\Automattic\\WooCommerce\\Caches\\OrderCache';
	if ( class_exists( $order_cache_class ) && function_exists( 'wc_get_container' ) ) {
		try {
			$order_cache = wc_get_container()->get( $order_cache_class );
			if ( is_object( $order_cache ) && method_exists( $order_cache, 'remove' ) ) {
				$order_cache->remove( $order_id );
			}
		} catch ( Throwable $error ) {
			return new WP_Error(
				'wpagent_wc_order_readback_failed',
				__( 'Der aktuelle WooCommerce-Bestellzustand konnte nicht sicher aus dem nativen Cache gelesen werden. Wiederhole keinen schreibenden Vorgang automatisch und lies die Bestellung erneut.', 'wp-agent-companion' ),
				array( 'status' => 409, 'recovery_required' => true )
			);
		}
	}
	wp_cache_delete( 'order-items-' . $order_id, 'orders' );
	clean_post_cache( $order_id );
	$order = wc_get_order( $order_id );
	return $order instanceof WC_Order && ! $order instanceof WC_Order_Refund ? $order : false;
}

/** @return string[] */
function wpagent_wc_order_required_capabilities(): array {
	return array( 'read_private_shop_orders', 'edit_shop_orders' );
}

/** @return array<int,string[]> */
function wpagent_wc_order_capability_grants(): array {
	$stored = get_option( WPAGENT_COMPANION_OPT_WC_ORDER_GRANTS, array() );
	$grants = array();
	foreach ( is_array( $stored ) ? $stored : array() as $user_id => $capabilities ) {
		$user_id = absint( $user_id );
		if ( ! $user_id || ! is_array( $capabilities ) ) {
			continue;
		}
		$allowed = array_values( array_intersect( wpagent_wc_order_required_capabilities(), array_map( 'sanitize_key', $capabilities ) ) );
		if ( $allowed ) {
			$grants[ $user_id ] = $allowed;
		}
	}
	return array_slice( $grants, 0, 200, true );
}

/** @param array<int,string[]> $grants */
function wpagent_wc_store_order_capability_grants( array $grants ): bool {
	if ( $grants ) {
		update_option( WPAGENT_COMPANION_OPT_WC_ORDER_GRANTS, $grants, false );
	} else {
		delete_option( WPAGENT_COMPANION_OPT_WC_ORDER_GRANTS );
	}
	return wpagent_wc_order_capability_grants() === $grants;
}

function wpagent_wc_orders_available(): bool {
	return function_exists( 'wc_get_orders' )
		&& function_exists( 'wc_get_order' )
		&& function_exists( 'wc_get_order_notes' )
		&& function_exists( 'wc_get_order_note' )
		&& class_exists( 'WC_Order' );
}

function wpagent_wc_orders_missing_error(): WP_Error {
	return new WP_Error(
		'wpagent_wc_orders_missing',
		__( 'WooCommerce ist nicht aktiv oder seine Bestell-CRUD-Schnittstelle ist nicht vollständig verfügbar.', 'wp-agent-companion' ),
		array( 'status' => 400 )
	);
}

/**
 * WooCommerce maps limit=-1 to MySQL's unsigned 64-bit maximum. The official
 * SQLite integration rejects that value because SQLite accepts only signed
 * 64-bit LIMIT values. PHP's integer maximum preserves the unbounded query
 * semantics on supported WordPress hosts without changing MySQL.
 *
 * @param array<string,mixed> $args
 * @return array<string,mixed>
 */
function wpagent_wc_bound_unlimited_refund_query_for_sqlite( array $args ): array {
	global $wpdb;
	if ( ! $wpdb instanceof WP_SQLite_DB || -1 !== (int) ( $args['limit'] ?? 0 ) ) {
		return $args;
	}
	$types = array_map( 'sanitize_key', (array) ( $args['type'] ?? $args['post_type'] ?? array() ) );
	if ( in_array( 'shop_order_refund', $types, true ) ) {
		$args['limit'] = PHP_INT_MAX;
	}
	return $args;
}
add_filter( 'woocommerce_order_query_args', 'wpagent_wc_bound_unlimited_refund_query_for_sqlite', 20 );

/**
 * Ability GET and DELETE requests transport nested booleans as query strings.
 * Accept only the explicit true representations so that "false" can never
 * enable personal or payment data by being a non-empty string.
 *
 * @param mixed $value
 */
function wpagent_wc_input_is_true( $value ): bool {
	return true === $value || 1 === $value || '1' === $value || ( is_string( $value ) && 'true' === strtolower( trim( $value ) ) );
}

/** @return array{enabled:bool,capabilities:string[],tracked:string[]}|WP_Error */
function wpagent_wc_order_user_access( int $user_id ) {
	$user = get_user_by( 'id', $user_id );
	if ( ! $user instanceof WP_User || ! wpagent_companion_is_safe_connection_user( $user ) ) {
		return new WP_Error( 'wpagent_wc_order_unsafe_user', __( 'Wähle einen Redakteur ohne Administrator- oder Super-Administratorrechte.', 'wp-agent-companion' ), array( 'status' => 400 ) );
	}
	$capabilities = array_values(
		array_filter(
			wpagent_wc_order_required_capabilities(),
			static fn( string $capability ): bool => user_can( $user, $capability )
		)
	);
	$grants = wpagent_wc_order_capability_grants();
	return array(
		'enabled'      => count( $capabilities ) === count( wpagent_wc_order_required_capabilities() ),
		'capabilities' => $capabilities,
		'tracked'      => $grants[ $user_id ] ?? array(),
	);
}

/**
 * Orders contain personal, address, payment, and communication data. These
 * capabilities are never granted together with ordinary catalog access.
 *
 * @return array{enabled:bool,capabilities:string[],tracked:string[]}|WP_Error
 */
function wpagent_wc_set_order_user_access( int $user_id, bool $enabled ) {
	$user = get_user_by( 'id', $user_id );
	if ( ! $user instanceof WP_User || ! wpagent_companion_is_safe_connection_user( $user ) ) {
		return new WP_Error( 'wpagent_wc_order_unsafe_user', __( 'Wähle einen Redakteur ohne Administrator- oder Super-Administratorrechte.', 'wp-agent-companion' ), array( 'status' => 400 ) );
	}
	if ( $enabled && ! wpagent_wc_orders_available() ) {
		return wpagent_wc_orders_missing_error();
	}
	$lock = wpagent_companion_acquire_connection_lock();
	if ( is_wp_error( $lock ) ) {
		return $lock;
	}
	try {
		$grants          = wpagent_wc_order_capability_grants();
		$original_grants = $grants;
		$tracked         = $grants[ $user_id ] ?? array();
		$newly_added     = array();
		if ( $enabled ) {
			foreach ( wpagent_wc_order_required_capabilities() as $capability ) {
				if ( in_array( $capability, $tracked, true ) ) {
					$user->add_cap( $capability );
					continue;
				}
				if ( ! user_can( $user, $capability ) ) {
					$user->add_cap( $capability );
					$tracked[]     = $capability;
					$newly_added[] = $capability;
				}
			}
			$tracked = array_values( array_unique( $tracked ) );
			if ( $tracked ) {
				$grants[ $user_id ] = $tracked;
			} else {
				unset( $grants[ $user_id ] );
			}
			if ( ! wpagent_wc_store_order_capability_grants( $grants ) ) {
				foreach ( $newly_added as $capability ) {
					$user->remove_cap( $capability );
				}
				wpagent_wc_store_order_capability_grants( $original_grants );
				clean_user_cache( $user_id );
				return new WP_Error( 'wpagent_wc_order_grant_store_failed', __( 'Die WooCommerce-Bestellrechte konnten nicht sicher gespeichert werden.', 'wp-agent-companion' ), array( 'status' => 500 ) );
			}
		} else {
			foreach ( $tracked as $capability ) {
				$user->remove_cap( $capability );
			}
			clean_user_cache( $user_id );
			$fresh_user = get_user_by( 'id', $user_id );
			$remaining  = $fresh_user instanceof WP_User
				? array_values( array_intersect( $tracked, array_keys( array_filter( (array) $fresh_user->caps ) ) ) )
				: $tracked;
			if ( $remaining ) {
				foreach ( $tracked as $capability ) {
					$user->add_cap( $capability );
				}
				clean_user_cache( $user_id );
				return new WP_Error( 'wpagent_wc_order_grant_remove_failed', __( 'Die von WPAgently ergänzten WooCommerce-Bestellrechte konnten nicht vollständig entfernt werden.', 'wp-agent-companion' ), array( 'status' => 500 ) );
			}
			unset( $grants[ $user_id ] );
			if ( ! wpagent_wc_store_order_capability_grants( $grants ) ) {
				foreach ( $tracked as $capability ) {
					$user->add_cap( $capability );
				}
				clean_user_cache( $user_id );
				return new WP_Error( 'wpagent_wc_order_grant_store_failed', __( 'Die WooCommerce-Bestellrechte konnten nicht sicher gespeichert werden.', 'wp-agent-companion' ), array( 'status' => 500 ) );
			}
		}
		clean_user_cache( $user_id );
		$access = wpagent_wc_order_user_access( $user_id );
		if ( $enabled && ( is_wp_error( $access ) || ! $access['enabled'] ) ) {
			foreach ( $newly_added as $capability ) {
				$user->remove_cap( $capability );
			}
			wpagent_wc_store_order_capability_grants( $original_grants );
			clean_user_cache( $user_id );
			return is_wp_error( $access ) ? $access : new WP_Error( 'wpagent_wc_order_grant_failed', __( 'Die WooCommerce-Bestellrechte konnten nicht vollständig aktiviert werden.', 'wp-agent-companion' ), array( 'status' => 500 ) );
		}
		return $access;
	} finally {
		wpagent_companion_release_connection_lock( $lock );
	}
}

/** @param mixed $input */
function wpagent_perm_wc_orders_read( $input = null ): bool {
	return current_user_can( 'read_private_shop_orders' );
}

/** @param mixed $input */
function wpagent_perm_wc_orders_edit( $input = null ): bool {
	return current_user_can( 'edit_shop_orders' );
}

/** @param mixed $value */
function wpagent_wc_order_date( $value ): ?string {
	return $value instanceof WC_DateTime ? $value->date( DATE_ATOM ) : null;
}

function wpagent_wc_order_response_character_length( string $value ): int {
	$bytes      = strlen( $value );
	$characters = 0;
	for ( $index = 0; $index < $bytes; $index++ ) {
		if ( ( ord( $value[ $index ] ) & 0xC0 ) !== 0x80 ) {
			$characters++;
		}
	}
	return $characters;
}

/** @return true|WP_Error */
function wpagent_wc_order_response_text_within_bounds( $value, int $maximum_characters, string $code ) {
	if ( ! is_scalar( $value ) || strlen( (string) $value ) > WPAGENT_WC_ORDER_MAX_RESPONSE_TEXT_BYTES || wpagent_wc_order_response_character_length( (string) $value ) > $maximum_characters ) {
		return new WP_Error( $code, __( 'WooCommerce hat einen zu großen oder ungültigen Bestellwert geliefert. Die Antwort wurde nicht unvollständig ausgegeben.', 'wp-agent-companion' ), array( 'status' => 502 ) );
	}
	return true;
}

/** @return true|WP_Error */
function wpagent_wc_order_response_integer_within_bounds( $value, int $minimum, string $code ) {
	if ( ! is_int( $value ) || $value < $minimum || $value > WPAGENT_WC_ORDER_MAX_RESPONSE_INTEGER ) {
		return new WP_Error( $code, __( 'WooCommerce hat eine ungültige Bestellkennung geliefert. Die Antwort wurde nicht unvollständig ausgegeben.', 'wp-agent-companion' ), array( 'status' => 502 ) );
	}
	return true;
}

/** @return true|WP_Error */
function wpagent_wc_order_response_number_within_bounds( $value, float $minimum, float $maximum, string $code ) {
	if ( ! is_numeric( $value ) || ! is_finite( (float) $value ) || (float) $value < $minimum || (float) $value > $maximum ) {
		return new WP_Error( $code, __( 'WooCommerce hat einen ungültigen Bestellwert geliefert. Die Antwort wurde nicht unvollständig ausgegeben.', 'wp-agent-companion' ), array( 'status' => 502 ) );
	}
	return true;
}

/** @return array<string,string> */
function wpagent_wc_order_address( WC_Order $order, string $type ): array {
	$address = $order->get_address( $type );
	$result  = array();
	foreach ( array( 'first_name', 'last_name', 'company', 'address_1', 'address_2', 'city', 'state', 'postcode', 'country', 'email', 'phone' ) as $key ) {
		if ( array_key_exists( $key, $address ) ) {
			$result[ $key ] = sanitize_text_field( (string) $address[ $key ] );
		}
	}
	return $result;
}

/** @return list<array<string,mixed>> */
function wpagent_wc_order_items( WC_Order $order ): array {
	$items = array();
	foreach ( $order->get_items( 'line_item' ) as $item_id => $item ) {
		if ( ! $item instanceof WC_Order_Item_Product ) {
			continue;
		}
		$items[] = array(
			'item_id'      => absint( $item_id ),
			'product_id'   => $item->get_product_id(),
			'variation_id' => $item->get_variation_id(),
			'name'         => sanitize_text_field( $item->get_name() ),
			'quantity'     => (float) $item->get_quantity(),
			'subtotal'     => (string) $item->get_subtotal(),
			'subtotal_tax' => (string) $item->get_subtotal_tax(),
			'total'        => (string) $item->get_total(),
			'total_tax'    => (string) $item->get_total_tax(),
		);
	}
	return $items;
}

/** @return true|WP_Error */
function wpagent_wc_order_validate_read_payload( WC_Order $order, bool $include_customer, bool $include_payment, bool $include_items ) {
	$valid = wpagent_wc_order_response_integer_within_bounds( $order->get_id(), 1, 'wpagent_wc_order_id_invalid' );
	if ( is_wp_error( $valid ) ) {
		return $valid;
	}
	$core_fields = array(
		array( $order->get_order_number(), 64, 'wpagent_wc_order_number_too_long' ),
		array( $order->get_status(), 64, 'wpagent_wc_order_status_too_long' ),
		array( $order->get_currency(), 3, 'wpagent_wc_order_currency_too_long' ),
		array( $order->get_total(), 32, 'wpagent_wc_order_total_too_long' ),
		array( $order->get_total_tax(), 32, 'wpagent_wc_order_total_tax_too_long' ),
		array( $order->get_shipping_total(), 32, 'wpagent_wc_order_shipping_total_too_long' ),
		array( $order->get_discount_total(), 32, 'wpagent_wc_order_discount_total_too_long' ),
		array( $order->get_total_refunded(), 32, 'wpagent_wc_order_refunded_total_too_long' ),
	);
	foreach ( $core_fields as $field ) {
		$valid = wpagent_wc_order_response_text_within_bounds( $field[0], $field[1], $field[2] );
		if ( is_wp_error( $valid ) ) {
			return $valid;
		}
	}
	foreach ( array( $order->get_date_created(), $order->get_date_modified(), $order->get_date_completed() ) as $date ) {
		$valid = wpagent_wc_order_response_text_within_bounds( wpagent_wc_order_date( $date ) ?? '', 40, 'wpagent_wc_order_date_too_long' );
		if ( is_wp_error( $valid ) ) {
			return $valid;
		}
	}
	if ( $include_items ) {
		$line_items = $order->get_items( 'line_item' );
		if ( count( $line_items ) > WPAGENT_WC_ORDER_MAX_RESPONSE_ITEMS ) {
			return new WP_Error( 'wpagent_wc_order_items_limit_exceeded', __( 'Diese WooCommerce-Bestellung hat mehr Positionen als sicher in einer Antwort ausgegeben werden können. Die Antwort wurde nicht gekürzt.', 'wp-agent-companion' ), array( 'status' => 502 ) );
		}
		foreach ( $line_items as $item_id => $item ) {
			if ( ! $item instanceof WC_Order_Item_Product ) {
				return new WP_Error( 'wpagent_wc_order_item_invalid', __( 'WooCommerce hat eine ungültige Bestellposition geliefert. Die Antwort wurde nicht unvollständig ausgegeben.', 'wp-agent-companion' ), array( 'status' => 502 ) );
			}
			foreach ( array(
				array( $item_id, 1, 'wpagent_wc_order_item_id_invalid' ),
				array( $item->get_product_id(), 0, 'wpagent_wc_order_product_id_invalid' ),
				array( $item->get_variation_id(), 0, 'wpagent_wc_order_variation_id_invalid' ),
			) as $identifier ) {
				$valid = wpagent_wc_order_response_integer_within_bounds( $identifier[0], $identifier[1], $identifier[2] );
				if ( is_wp_error( $valid ) ) {
					return $valid;
				}
			}
			foreach ( array(
				array( $item->get_name(), 250, 'wpagent_wc_order_item_name_too_long' ),
				array( $item->get_subtotal(), 32, 'wpagent_wc_order_item_subtotal_too_long' ),
				array( $item->get_subtotal_tax(), 32, 'wpagent_wc_order_item_subtotal_tax_too_long' ),
				array( $item->get_total(), 32, 'wpagent_wc_order_item_total_too_long' ),
				array( $item->get_total_tax(), 32, 'wpagent_wc_order_item_total_tax_too_long' ),
			) as $field ) {
				$valid = wpagent_wc_order_response_text_within_bounds( $field[0], $field[1], $field[2] );
				if ( is_wp_error( $valid ) ) {
					return $valid;
				}
			}
			$valid = wpagent_wc_order_response_number_within_bounds( $item->get_quantity(), -1000000, 1000000, 'wpagent_wc_order_item_quantity_invalid' );
			if ( is_wp_error( $valid ) ) {
				return $valid;
			}
		}
	}
	if ( $include_customer ) {
		$valid = wpagent_wc_order_response_integer_within_bounds( $order->get_customer_id(), 0, 'wpagent_wc_order_customer_id_invalid' );
		if ( is_wp_error( $valid ) ) {
			return $valid;
		}
		$address_limits = array( 'first_name' => 100, 'last_name' => 100, 'company' => 160, 'address_1' => 250, 'address_2' => 250, 'city' => 100, 'state' => 100, 'postcode' => 32, 'country' => 2, 'email' => 320, 'phone' => 64 );
		foreach ( array( 'billing', 'shipping' ) as $type ) {
			$address = $order->get_address( $type );
			foreach ( $address_limits as $key => $maximum ) {
				if ( array_key_exists( $key, $address ) ) {
					$valid = wpagent_wc_order_response_text_within_bounds( $address[ $key ], $maximum, 'wpagent_wc_order_address_' . $key . '_too_long' );
					if ( is_wp_error( $valid ) ) {
						return $valid;
					}
				}
			}
		}
		$valid = wpagent_wc_order_response_text_within_bounds( $order->get_customer_note(), 2000, 'wpagent_wc_order_customer_note_too_long' );
		if ( is_wp_error( $valid ) ) {
			return $valid;
		}
	}
	if ( $include_payment ) {
		foreach ( array(
			array( $order->get_payment_method(), 64, 'wpagent_wc_order_payment_method_too_long' ),
			array( $order->get_payment_method_title(), 250, 'wpagent_wc_order_payment_method_title_too_long' ),
			array( $order->get_transaction_id(), 250, 'wpagent_wc_order_transaction_id_too_long' ),
			array( wpagent_wc_order_date( $order->get_date_paid() ) ?? '', 40, 'wpagent_wc_order_date_paid_too_long' ),
		) as $field ) {
			$valid = wpagent_wc_order_response_text_within_bounds( $field[0], $field[1], $field[2] );
			if ( is_wp_error( $valid ) ) {
				return $valid;
			}
		}
	}
	return true;
}

/** @return array<string,mixed>|WP_Error */
function wpagent_wc_order_read_payload( WC_Order $order, bool $include_customer = false, bool $include_payment = false, bool $include_items = false ) {
	$valid = wpagent_wc_order_validate_read_payload( $order, $include_customer, $include_payment, $include_items );
	return is_wp_error( $valid ) ? $valid : wpagent_wc_order_payload( $order, $include_customer, $include_payment, $include_items );
}

/** @return array<string,mixed> */
function wpagent_wc_order_payload( WC_Order $order, bool $include_customer = false, bool $include_payment = false, bool $include_items = false ): array {
	$line_items = $order->get_items( 'line_item' );
	$payload    = array(
		'provider'        => 'woocommerce',
		'order_id'        => $order->get_id(),
		'number'          => (string) $order->get_order_number(),
		'status'          => $order->get_status(),
		'currency'        => $order->get_currency(),
		'date_created'    => wpagent_wc_order_date( $order->get_date_created() ),
		'date_modified'   => wpagent_wc_order_date( $order->get_date_modified() ),
		'date_completed'  => wpagent_wc_order_date( $order->get_date_completed() ),
		'total'           => (string) $order->get_total(),
		'total_tax'       => (string) $order->get_total_tax(),
		'shipping_total'  => (string) $order->get_shipping_total(),
		'discount_total'  => (string) $order->get_discount_total(),
		'refunded_total'  => (string) $order->get_total_refunded(),
		'item_count'      => count( $line_items ),
		'items_truncated' => false,
	);
	$payload['state_hash'] = hash( 'sha256', (string) wp_json_encode( $payload, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE ) );
	if ( $include_items ) {
		$payload['items'] = wpagent_wc_order_items( $order );
	}
	if ( $include_customer ) {
		$payload['customer'] = array(
			'customer_id'   => $order->get_customer_id(),
			'billing'       => wpagent_wc_order_address( $order, 'billing' ),
			'shipping'      => wpagent_wc_order_address( $order, 'shipping' ),
			'customer_note' => sanitize_textarea_field( $order->get_customer_note() ),
		);
	}
	if ( $include_payment ) {
		$payload['payment'] = array(
			'method'         => sanitize_text_field( $order->get_payment_method() ),
			'method_title'   => sanitize_text_field( $order->get_payment_method_title() ),
			'transaction_id' => sanitize_text_field( $order->get_transaction_id() ),
			'date_paid'      => wpagent_wc_order_date( $order->get_date_paid() ),
		);
	}
	return $payload;
}

/** @return array<string,mixed> */
function wpagent_wc_order_output_schema(): array {
	$address_schema  = array(
		'type'                 => 'object',
		'additionalProperties' => false,
		'properties'           => array(
			'first_name' => array( 'type' => 'string', 'maxLength' => 100 ),
			'last_name'  => array( 'type' => 'string', 'maxLength' => 100 ),
			'company'    => array( 'type' => 'string', 'maxLength' => 160 ),
			'address_1'  => array( 'type' => 'string', 'maxLength' => 250 ),
			'address_2'  => array( 'type' => 'string', 'maxLength' => 250 ),
			'city'       => array( 'type' => 'string', 'maxLength' => 100 ),
			'state'      => array( 'type' => 'string', 'maxLength' => 100 ),
			'postcode'   => array( 'type' => 'string', 'maxLength' => 32 ),
			'country'    => array( 'type' => 'string', 'maxLength' => 2 ),
			'email'      => array( 'type' => 'string', 'maxLength' => 320 ),
			'phone'      => array( 'type' => 'string', 'maxLength' => 64 ),
		),
	);
	return array(
		'type'                 => 'object',
		'additionalProperties' => false,
		'properties'           => array(
			'provider'        => array( 'type' => 'string', 'maxLength' => 12 ),
			'order_id'        => array( 'type' => 'integer', 'minimum' => 1, 'maximum' => WPAGENT_WC_ORDER_MAX_RESPONSE_INTEGER ),
			'number'          => array( 'type' => 'string', 'maxLength' => 64 ),
			'status'          => array( 'type' => 'string', 'maxLength' => 64 ),
			'currency'        => array( 'type' => 'string', 'maxLength' => 3 ),
			'date_created'    => array( 'type' => array( 'string', 'null' ), 'maxLength' => 40 ),
			'date_modified'   => array( 'type' => array( 'string', 'null' ), 'maxLength' => 40 ),
			'date_completed'  => array( 'type' => array( 'string', 'null' ), 'maxLength' => 40 ),
			'total'           => array( 'type' => 'string', 'maxLength' => 32 ),
			'total_tax'       => array( 'type' => 'string', 'maxLength' => 32 ),
			'shipping_total'  => array( 'type' => 'string', 'maxLength' => 32 ),
			'discount_total'  => array( 'type' => 'string', 'maxLength' => 32 ),
			'refunded_total'  => array( 'type' => 'string', 'maxLength' => 32 ),
			'item_count'      => array( 'type' => 'integer', 'minimum' => 0, 'maximum' => WPAGENT_WC_ORDER_MAX_RESPONSE_INTEGER ),
			'items_truncated' => array( 'type' => 'boolean' ),
			'state_hash'      => array( 'type' => 'string', 'pattern' => '^[a-f0-9]{64}$' ),
			'items'           => array(
				'type'     => 'array',
				'maxItems' => WPAGENT_WC_ORDER_MAX_RESPONSE_ITEMS,
				'items'    => array(
					'type'                 => 'object',
					'additionalProperties' => false,
					'properties'           => array(
						'item_id'      => array( 'type' => 'integer', 'minimum' => 1, 'maximum' => WPAGENT_WC_ORDER_MAX_RESPONSE_INTEGER ),
						'product_id'   => array( 'type' => 'integer', 'minimum' => 0, 'maximum' => WPAGENT_WC_ORDER_MAX_RESPONSE_INTEGER ),
						'variation_id' => array( 'type' => 'integer', 'minimum' => 0, 'maximum' => WPAGENT_WC_ORDER_MAX_RESPONSE_INTEGER ),
						'name'         => array( 'type' => 'string', 'maxLength' => 250 ),
						'quantity'     => array( 'type' => 'number', 'minimum' => -1000000, 'maximum' => 1000000 ),
						'subtotal'     => array( 'type' => 'string', 'maxLength' => 32 ),
						'subtotal_tax' => array( 'type' => 'string', 'maxLength' => 32 ),
						'total'        => array( 'type' => 'string', 'maxLength' => 32 ),
						'total_tax'    => array( 'type' => 'string', 'maxLength' => 32 ),
					),
				),
			),
			'customer'        => array(
				'type'                 => 'object',
				'additionalProperties' => false,
				'properties'           => array(
					'customer_id'   => array( 'type' => 'integer', 'minimum' => 0, 'maximum' => WPAGENT_WC_ORDER_MAX_RESPONSE_INTEGER ),
					'billing'       => $address_schema,
					'shipping'      => $address_schema,
					'customer_note' => array( 'type' => 'string', 'maxLength' => 2000 ),
				),
			),
			'payment'         => array(
				'type'                 => 'object',
				'additionalProperties' => false,
				'properties'           => array(
					'method'         => array( 'type' => 'string', 'maxLength' => 64 ),
					'method_title'   => array( 'type' => 'string', 'maxLength' => 250 ),
					'transaction_id' => array( 'type' => 'string', 'maxLength' => 250 ),
					'date_paid'      => array( 'type' => array( 'string', 'null' ), 'maxLength' => 40 ),
				),
			),
		),
		'required'             => array( 'provider', 'order_id', 'number', 'status', 'currency', 'date_created', 'date_modified', 'date_completed', 'total', 'total_tax', 'shipping_total', 'discount_total', 'refunded_total', 'item_count', 'items_truncated', 'state_hash' ),
	);
}

/** @return array<string,mixed> */
function wpagent_wc_order_note_payload( object $note ): array {
	return array(
		'note_id'       => absint( $note->id ?? 0 ),
		'order_id'      => absint( $note->order_id ?? 0 ),
		'date_created'  => wpagent_wc_order_date( $note->date_created ?? null ),
		'content'       => sanitize_textarea_field( wp_strip_all_tags( (string) ( $note->content ?? '' ) ) ),
		'customer_note' => ! empty( $note->customer_note ),
		'added_by'      => sanitize_text_field( (string) ( $note->added_by ?? '' ) ),
	);
}

/** @return array<string,mixed> */
function wpagent_wc_order_note_output_schema(): array {
	return array(
		'type'                 => 'object',
		'additionalProperties' => false,
		'properties'           => array(
			'note_id'       => array( 'type' => 'integer', 'minimum' => 1, 'maximum' => WPAGENT_WC_ORDER_MAX_RESPONSE_INTEGER ),
			'order_id'      => array( 'type' => 'integer', 'minimum' => 1, 'maximum' => WPAGENT_WC_ORDER_MAX_RESPONSE_INTEGER ),
			'date_created'  => array( 'type' => array( 'string', 'null' ), 'maxLength' => 40 ),
			'content'       => array( 'type' => 'string', 'maxLength' => WPAGENT_WC_ORDER_MAX_NOTE_BYTES ),
			'customer_note' => array( 'type' => 'boolean' ),
			'added_by'      => array( 'type' => 'string', 'maxLength' => 100 ),
		),
		'required'             => array( 'note_id', 'order_id', 'date_created', 'content', 'customer_note', 'added_by' ),
	);
}

/** @return array<string,mixed>|WP_Error */
function wpagent_wc_order_note_read_payload( object $note, int $expected_order_id ) {
	$note_id  = $note->id ?? null;
	$order_id = $note->order_id ?? null;
	if ( is_wp_error( wpagent_wc_order_response_integer_within_bounds( $note_id, 1, 'wpagent_wc_order_note_id_invalid' ) ) || is_wp_error( wpagent_wc_order_response_integer_within_bounds( $order_id, 1, 'wpagent_wc_order_note_order_id_invalid' ) ) || $expected_order_id !== $order_id ) {
		return new WP_Error( 'wpagent_wc_order_note_invalid', __( 'WooCommerce hat eine ungültige oder fremde Bestellnotiz geliefert. Die Antwort wurde nicht unvollständig ausgegeben.', 'wp-agent-companion' ), array( 'status' => 502 ) );
	}
	foreach ( array(
		array( $note->content ?? '', WPAGENT_WC_ORDER_MAX_NOTE_CHARACTERS, 'wpagent_wc_order_note_content_too_long' ),
		array( $note->added_by ?? '', 100, 'wpagent_wc_order_note_added_by_too_long' ),
		array( wpagent_wc_order_date( $note->date_created ?? null ) ?? '', 40, 'wpagent_wc_order_note_date_too_long' ),
	) as $field ) {
		$valid = wpagent_wc_order_response_text_within_bounds( $field[0], $field[1], $field[2] );
		if ( is_wp_error( $valid ) ) {
			return $valid;
		}
	}
	return wpagent_wc_order_note_payload( $note );
}

/** @param mixed $input @return WC_Order|WP_Error */
function wpagent_wc_get_authorized_order( $input, string $capability ) {
	if ( ! wpagent_wc_orders_available() ) {
		return wpagent_wc_orders_missing_error();
	}
	$input    = is_array( $input ) ? $input : array();
	$order_id = absint( $input['order_id'] ?? 0 );
	if ( $order_id <= 0 ) {
		return new WP_Error( 'wpagent_wc_bad_order_id', __( 'order_id muss eine positive Ganzzahl sein.', 'wp-agent-companion' ), array( 'status' => 400 ) );
	}
	if ( ! current_user_can( $capability ) ) {
		return new WP_Error( 'wpagent_forbidden', __( 'Keine Berechtigung für diese WooCommerce-Bestellung.', 'wp-agent-companion' ), array( 'status' => 403 ) );
	}
	$order = wc_get_order( $order_id );
	if ( ! $order instanceof WC_Order || $order instanceof WC_Order_Refund ) {
		return new WP_Error( 'wpagent_wc_order_not_found', __( 'WooCommerce-Bestellung nicht gefunden.', 'wp-agent-companion' ), array( 'status' => 404 ) );
	}
	return $order;
}

/** @return string|WP_Error */
function wpagent_wc_normalize_order_status( $raw, bool $for_write = false ) {
	$status = sanitize_key( (string) $raw );
	$status = str_starts_with( $status, 'wc-' ) ? substr( $status, 3 ) : $status;
	$known  = array_map( static fn( string $key ): string => str_starts_with( $key, 'wc-' ) ? substr( $key, 3 ) : $key, array_keys( wc_get_order_statuses() ) );
	if ( '' === $status || ! in_array( $status, $known, true ) ) {
		return new WP_Error( 'wpagent_wc_bad_order_status', __( 'Der angeforderte Bestellstatus ist nicht registriert.', 'wp-agent-companion' ), array( 'status' => 400 ) );
	}
	if ( $for_write && in_array( $status, array( 'refunded', 'checkout-draft' ), true ) ) {
		return new WP_Error( 'wpagent_wc_unsafe_order_status', __( 'Erstattet und Checkout-Entwurf dürfen nicht als bloßer Status gesetzt werden. Eine Erstattung benötigt einen eigenen Zahlungs- und Betragsvorgang.', 'wp-agent-companion' ), array( 'status' => 400 ) );
	}
	return $status;
}

function wpagent_wc_order_expected_version_matches( array $input, WC_Order $order ): bool {
	$expected_hash = strtolower( (string) ( $input['expected_state_hash'] ?? '' ) );
	$current_hash  = (string) ( wpagent_wc_order_payload( $order )['state_hash'] ?? '' );
	if ( ! preg_match( '/^[a-f0-9]{64}$/', $expected_hash ) || ! hash_equals( $current_hash, $expected_hash ) ) {
		return false;
	}
	$expected_date = trim( (string) ( $input['expected_date_modified'] ?? '' ) );
	$actual_date   = wpagent_wc_order_date( $order->get_date_modified() ) ?? '';
	return '' === $expected_date || hash_equals( $expected_date, $actual_date );
}
