<?php
/**
 * Gemeinsame WooCommerce-Helfer für Produkt- und Varianten-Abilities.
 *
 * Alle Lese- und Schreibpfade verwenden ausschließlich die öffentlichen
 * WooCommerce-Objekte und Data Stores. Rohe Postmeta- oder SQL-Zugriffe sind
 * hier bewusst ausgeschlossen, damit Lookup-Tabellen und Objekt-Caches
 * konsistent bleiben.
 *
 * @package WPAgentCompanion
 */

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

define( 'WPAGENT_WC_PRODUCT_LOCK_PREFIX', 'wp_agent_wc_product_lock_' );
define( 'WPAGENT_WC_PRODUCT_LOCK_TTL', 30 );
define( 'WPAGENT_WC_VARIATION_DUPLICATE_PAGE_SIZE', 25 );

/**
 * @return true|WP_Error
 */
function wpagent_require_woocommerce() {
	if ( ! function_exists( 'wc_get_product' ) || ! class_exists( 'WC_Product' ) ) {
		return new WP_Error( 'wpagent_wc_missing', __( 'WooCommerce ist nicht aktiv.', 'wp-agent-companion' ), array( 'status' => 400 ) );
	}
	return true;
}

/**
 * @return WC_Product|WP_Error
 */
function wpagent_get_editable_product( int $product_id ) {
	$available = wpagent_require_woocommerce();
	if ( is_wp_error( $available ) ) {
		return $available;
	}
	// WooCommerce object hydration exposes product type and cached fields. Test
	// the input-bound post meta-cap before resolving that target state.
	if ( ! current_user_can( 'edit_post', $product_id ) ) {
		return new WP_Error( 'wpagent_forbidden', __( 'Keine Berechtigung.', 'wp-agent-companion' ), array( 'status' => 403 ) );
	}
	$product = wc_get_product( $product_id );
	if ( ! $product || $product->is_type( 'variation' ) ) {
		return new WP_Error( 'wpagent_forbidden', __( 'Keine Berechtigung.', 'wp-agent-companion' ), array( 'status' => 403 ) );
	}
	return $product;
}

/**
 * @return array<string, mixed>
 */
function wpagent_wc_product_payload( WC_Product $product, bool $strict_read = false ): array {
	$attributes = array();
	foreach ( $product->get_attributes() as $attribute ) {
		if ( ! $attribute instanceof WC_Product_Attribute ) {
			if ( $strict_read ) {
				throw new UnexpectedValueException( 'WooCommerce returned an invalid product attribute.' );
			}
			continue;
		}
		$options = $attribute->is_taxonomy()
			? wc_get_product_terms( $product->get_id(), $attribute->get_name(), array( 'fields' => 'names' ) )
			: $attribute->get_options();
		if ( ! is_array( $options ) ) {
			if ( $strict_read ) {
				throw new UnexpectedValueException( 'WooCommerce returned invalid product attribute options.' );
			}
			$options = array();
		}
		$attributes[] = array(
			'id'        => (int) $attribute->get_id(),
			'name'      => $attribute->get_name(),
			'taxonomy'  => $attribute->is_taxonomy(),
			'options'   => array_values( $options ),
			'visible'   => $attribute->get_visible(),
			'variation' => $attribute->get_variation(),
			'position'  => $attribute->get_position(),
		);
	}

	$payload = array(
		'product_id'        => $product->get_id(),
		'type'              => $product->get_type(),
		'status'            => $product->get_status(),
		'name'              => $product->get_name(),
		'slug'              => $product->get_slug(),
		'sku'               => $product->get_sku(),
		'regular_price'     => $product->get_regular_price(),
		'sale_price'        => $product->get_sale_price(),
		'price'             => $product->get_price(),
		'stock_quantity'    => $product->get_stock_quantity(),
		'manage_stock'      => (bool) $product->get_manage_stock(),
		'stock_status'      => $product->get_stock_status(),
		'short_description' => $product->get_short_description(),
		'description'       => $product->get_description(),
		'virtual'           => $product->get_virtual(),
		'downloadable'      => $product->get_downloadable(),
		'featured'          => $product->get_featured(),
		'catalog_visibility'=> $product->get_catalog_visibility(),
		'weight'            => $product->get_weight(),
		'dimensions'        => array(
			'length' => $product->get_length(),
			'width'  => $product->get_width(),
			'height' => $product->get_height(),
		),
		'image_id'          => (int) $product->get_image_id(),
		'gallery_image_ids' => array_values( array_map( 'intval', $product->get_gallery_image_ids() ) ),
		'category_ids'      => array_values( array_map( 'intval', $product->get_category_ids() ) ),
		'tag_ids'           => array_values( array_map( 'intval', $product->get_tag_ids() ) ),
		'shipping_class_id' => (int) $product->get_shipping_class_id(),
		'attributes'        => $attributes,
	);
	return wpagent_wc_payload_with_state_hash( $payload );
}

/**
 * @return array<string, mixed>
 */
function wpagent_wc_variation_payload( WC_Product_Variation $variation ): array {
	$payload = array(
		'variation_id'  => $variation->get_id(),
		'parent_id'     => $variation->get_parent_id(),
		'status'        => $variation->get_status(),
		'sku'           => $variation->get_sku(),
		'regular_price' => $variation->get_regular_price(),
		'sale_price'    => $variation->get_sale_price(),
		'price'         => $variation->get_price(),
		'manage_stock'  => (bool) $variation->get_manage_stock(),
		'stock_quantity'=> $variation->get_stock_quantity(),
		'stock_status'  => $variation->get_stock_status(),
		'virtual'       => $variation->get_virtual(),
		'downloadable'  => $variation->get_downloadable(),
		'weight'        => $variation->get_weight(),
		'dimensions'    => array(
			'length' => $variation->get_length(),
			'width'  => $variation->get_width(),
			'height' => $variation->get_height(),
		),
		'image_id'       => (int) $variation->get_image_id(),
		'description'    => $variation->get_description(),
		'attributes'     => $variation->get_attributes(),
	);
	return wpagent_wc_payload_with_state_hash( $payload );
}

/**
 * Hashes exactly the public, native WooCommerce state returned to an agent.
 * Object-map keys are sorted recursively while ordered lists stay ordered, so
 * the hash is stable across PHP array construction order without hiding a
 * meaningful list reorder.
 *
 * @param array<string,mixed> $payload
 * @return array<string,mixed>
 */
function wpagent_wc_payload_with_state_hash( array $payload ): array {
	unset( $payload['state_hash'] );
	$payload['state_hash'] = wpagent_wc_state_hash( $payload );
	return $payload;
}

/** @param array<string,mixed> $payload */
function wpagent_wc_state_hash( array $payload ): string {
	$canonical = wpagent_wc_canonicalize_state( $payload );
	$encoded   = wp_json_encode( $canonical, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE | JSON_INVALID_UTF8_SUBSTITUTE );
	if ( ! is_string( $encoded ) ) {
		$encoded = serialize( $canonical );
	}
	return hash( 'sha256', $encoded );
}

/** @return mixed */
function wpagent_wc_canonicalize_state( $value ) {
	if ( ! is_array( $value ) ) {
		return $value;
	}
	if ( array_is_list( $value ) ) {
		return array_map( 'wpagent_wc_canonicalize_state', $value );
	}
	ksort( $value, SORT_STRING );
	foreach ( $value as $key => $item ) {
		$value[ $key ] = wpagent_wc_canonicalize_state( $item );
	}
	return $value;
}

/**
 * Requires the exact hash an agent obtained from a preceding read. The
 * current hash is deliberately returned on conflict, never a stale object.
 *
 * @param array<string,mixed> $input
 * @param array<string,mixed> $payload
 * @return true|WP_Error
 */
function wpagent_wc_require_expected_state_hash( array $input, array $payload, string $scope ) {
	$expected = strtolower( (string) ( $input['expected_state_hash'] ?? '' ) );
	$current  = (string) ( $payload['state_hash'] ?? '' );
	if ( ! preg_match( '/^[a-f0-9]{64}$/', $expected ) || ! preg_match( '/^[a-f0-9]{64}$/', $current ) || ! hash_equals( $current, $expected ) ) {
		return new WP_Error(
			'wpagent_wc_' . $scope . '_conflict',
			__( 'Der WooCommerce-Zustand hat sich seit dem Lesen geändert. Lies ihn neu und wiederhole den Vorgang.', 'wp-agent-companion' ),
			array( 'status' => 409, 'current_hash' => $current )
		);
	}
	return true;
}

/** @return string */
function wpagent_wc_product_lock_option( int $product_id ): string {
	return WPAGENT_WC_PRODUCT_LOCK_PREFIX . $product_id;
}

/** @return string|WP_Error */
function wpagent_wc_acquire_product_lock( int $product_id ) {
	$token = $product_id > 0
		? wpagent_companion_acquire_option_lock( wpagent_wc_product_lock_option( $product_id ), WPAGENT_WC_PRODUCT_LOCK_TTL )
		: false;
	if ( is_string( $token ) && '' !== $token ) {
		return $token;
	}
	return new WP_Error(
		'wpagent_wc_product_locked',
		__( 'Dieser WooCommerce-Produktvorgang wird gerade durch einen anderen Companion-Vorgang bearbeitet.', 'wp-agent-companion' ),
		array( 'status' => 409 )
	);
}

function wpagent_wc_release_product_lock( int $product_id, string $token ): void {
	wpagent_companion_release_option_lock( wpagent_wc_product_lock_option( $product_id ), $token );
}

function wpagent_wc_product_lock_checkpoint( int $product_id, string $token ): bool {
	$option = wpagent_wc_product_lock_option( $product_id );
	return wpagent_companion_refresh_option_lock( $option, $token, WPAGENT_WC_PRODUCT_LOCK_TTL )
		&& wpagent_companion_option_lock_is_owned( $option, $token );
}

/** @return WP_Error */
function wpagent_wc_recovery_required( string $scope, string $message, string $current_hash = '' ): WP_Error {
	$data = array( 'status' => 409, 'recovery_required' => true );
	if ( preg_match( '/^[a-f0-9]{64}$/', $current_hash ) ) {
		$data['current_hash'] = $current_hash;
	}
	return new WP_Error( 'wpagent_wc_' . $scope . '_recovery_required', $message, $data );
}

function wpagent_wc_forget_product_cache( int $product_id ): void {
	wc_delete_product_transients( $product_id );
	clean_post_cache( $product_id );
	wp_cache_delete( 'product-' . $product_id, 'products' );
	if ( class_exists( 'WC_Cache_Helper' ) ) {
		WC_Cache_Helper::invalidate_cache_group( 'product_' . $product_id );
	}
	$product_cache_class = '\\Automattic\\WooCommerce\\Internal\\Caches\\ProductCache';
	if ( class_exists( $product_cache_class ) && function_exists( 'wc_get_container' ) ) {
		$product_cache = wc_get_container()->get( $product_cache_class );
		if ( is_object( $product_cache ) && method_exists( $product_cache, 'remove' ) ) {
			$product_cache->remove( $product_id );
		}
	}
}

/** @return WC_Product|false */
function wpagent_wc_fresh_product( int $product_id ) {
	wpagent_wc_forget_product_cache( $product_id );
	$product = wc_get_product( $product_id );
	return $product instanceof WC_Product ? $product : false;
}

/**
 * Liefert einen stabil vergleichbaren Produktwert direkt aus dem Objekt.
 *
 * @return mixed
 */
function wpagent_wc_product_field_value( WC_Product $product, string $field ) {
	if ( 'attributes' === $field ) {
		$result = array();
		foreach ( $product->get_attributes() as $attribute ) {
			if ( $attribute instanceof WC_Product_Attribute ) {
				$result[] = array(
					'id'        => (int) $attribute->get_id(),
					'name'      => $attribute->get_name(),
					'options'   => array_values( $attribute->get_options() ),
					'visible'   => $attribute->get_visible(),
					'variation' => $attribute->get_variation(),
					'position'  => $attribute->get_position(),
				);
			}
		}
		return $result;
	}
	$getter = 'get_' . $field;
	return is_callable( array( $product, $getter ) ) ? $product->{$getter}() : null;
}

/**
 * @param list<string> $fields
 * @return array<string,mixed>
 */
function wpagent_wc_capture_product_fields( WC_Product $product, array $fields ): array {
	$result = array();
	foreach ( $fields as $field ) {
		$result[ $field ] = wpagent_wc_product_field_value( $product, $field );
	}
	return $result;
}

/**
 * @param array<string,mixed> $expected
 * @return array{written:array<string,bool>,verified:bool}
 */
function wpagent_wc_verify_product_fields( WC_Product $product, array $expected ): array {
	$written  = array();
	$verified = true;
	foreach ( $expected as $field => $value ) {
		$actual            = wpagent_wc_product_field_value( $product, $field );
		$ok                = $actual === $value;
		$written[ $field ] = $ok;
		$verified          = $verified && $ok;
	}
	return array( 'written' => $written, 'verified' => $verified );
}

/**
 * Liefert einen stabil vergleichbaren Variantenwert direkt aus dem Objekt.
 * Variantenattribute sind einfache Schlüssel-Wert-Paare und unterscheiden
 * sich damit von den WC_Product_Attribute-Objekten eines Elternprodukts.
 *
 * @return mixed
 */
function wpagent_wc_variation_field_value( WC_Product_Variation $variation, string $field ) {
	if ( 'attributes' === $field ) {
		return $variation->get_attributes();
	}
	$getter = 'get_' . $field;
	return is_callable( array( $variation, $getter ) ) ? $variation->{$getter}() : null;
}

/**
 * @param list<string> $fields
 * @return array<string,mixed>
 */
function wpagent_wc_capture_variation_fields( WC_Product_Variation $variation, array $fields ): array {
	$result = array();
	foreach ( $fields as $field ) {
		$result[ $field ] = wpagent_wc_variation_field_value( $variation, $field );
	}
	return $result;
}

/**
 * @param array<string,mixed> $expected
 * @return array{written:array<string,bool>,verified:bool}
 */
function wpagent_wc_verify_variation_fields( WC_Product_Variation $variation, array $expected ): array {
	$written  = array();
	$verified = true;
	foreach ( $expected as $field => $value ) {
		$actual            = wpagent_wc_variation_field_value( $variation, $field );
		$ok                = $actual === $value;
		$written[ $field ] = $ok;
		$verified          = $verified && $ok;
	}
	return array( 'written' => $written, 'verified' => $verified );
}

/**
 * @return true|WP_Error
 */
function wpagent_wc_validate_image_id( int $image_id, string $field ) {
	if ( 0 === $image_id ) {
		return true;
	}
	if ( $image_id < 0 || ! wp_attachment_is_image( $image_id ) ) {
		return new WP_Error(
			'wpagent_bad_product_value',
			sprintf(
				/* translators: %s: Feldname. */
				__( '%s muss auf einen vorhandenen Bildanhang zeigen.', 'wp-agent-companion' ),
				$field
			),
			array( 'status' => 400 )
		);
	}
	return true;
}

/**
 * @return true|WP_Error
 */
function wpagent_wc_require_assign_terms( string $taxonomy ) {
	$taxonomy_object = get_taxonomy( $taxonomy );
	if ( ! $taxonomy_object ) {
		return new WP_Error( 'wpagent_bad_product_value', __( 'Die angeforderte Produkttaxonomie ist nicht verfügbar.', 'wp-agent-companion' ), array( 'status' => 400 ) );
	}
	$capability = $taxonomy_object->cap->assign_terms ?? 'edit_posts';
	if ( ! current_user_can( $capability ) ) {
		return new WP_Error( 'wpagent_forbidden', __( 'Keine Berechtigung zum Zuweisen dieser Produkttaxonomie.', 'wp-agent-companion' ), array( 'status' => 403 ) );
	}
	return true;
}

/**
 * @param mixed  $raw
 * @param string $field
 * @return string|WP_Error
 */
function wpagent_sanitize_wc_price( $raw, string $field ) {
	if ( null === $raw || '' === $raw ) {
		return '';
	}
	if ( ! is_numeric( $raw ) || (float) $raw < 0 ) {
		return new WP_Error( 'wpagent_bad_product_value', sprintf( /* translators: %s: Feldname. */ __( '%s muss eine nicht negative Zahl sein.', 'wp-agent-companion' ), $field ), array( 'status' => 400 ) );
	}
	return (string) $raw;
}

/**
 * @param mixed  $raw
 * @param string $field
 * @return string|WP_Error
 */
function wpagent_sanitize_wc_dimension( $raw, string $field ) {
	if ( null === $raw || '' === $raw ) {
		return '';
	}
	if ( ! is_numeric( $raw ) || (float) $raw < 0 ) {
		return new WP_Error( 'wpagent_bad_product_value', sprintf( /* translators: %s: Feldname. */ __( '%s muss eine nicht negative Zahl sein.', 'wp-agent-companion' ), $field ), array( 'status' => 400 ) );
	}
	return (string) $raw;
}

/**
 * @param mixed $raw
 * @return list<WC_Product_Attribute>|WP_Error
 */
function wpagent_wc_build_attributes( $raw ) {
	if ( ! is_array( $raw ) ) {
		return new WP_Error( 'wpagent_bad_attributes', __( 'attributes muss eine Liste sein.', 'wp-agent-companion' ), array( 'status' => 400 ) );
	}
	$result = array();
	$seen   = array();
	foreach ( array_values( $raw ) as $position => $item ) {
		if ( ! is_array( $item ) ) {
			return new WP_Error( 'wpagent_bad_attributes', __( 'Jedes Attribut muss ein Objekt sein.', 'wp-agent-companion' ), array( 'status' => 400 ) );
		}
		$name = trim( (string) ( $item['name'] ?? '' ) );
		if ( '' === $name || ! isset( $item['options'] ) || ! is_array( $item['options'] ) || array() === $item['options'] ) {
			return new WP_Error( 'wpagent_bad_attributes', __( 'Jedes Attribut benötigt name und mindestens eine Option.', 'wp-agent-companion' ), array( 'status' => 400 ) );
		}
		$taxonomy = wc_attribute_taxonomy_name( $name );
		$is_global = taxonomy_exists( $name ) || taxonomy_exists( $taxonomy );
		$attribute_name = $is_global ? ( taxonomy_exists( $name ) ? $name : $taxonomy ) : $name;
		$key = sanitize_title( $attribute_name );
		if ( isset( $seen[ $key ] ) ) {
			return new WP_Error( 'wpagent_bad_attributes', sprintf( /* translators: %s: Attributname. */ __( 'Das Attribut "%s" wurde doppelt übergeben.', 'wp-agent-companion' ), $name ), array( 'status' => 400 ) );
		}
		$seen[ $key ] = true;

		$options = array_values( array_unique( array_filter( array_map( 'strval', $item['options'] ), static fn( string $value ): bool => '' !== trim( $value ) ) ) );
		if ( array() === $options ) {
			return new WP_Error( 'wpagent_bad_attributes', sprintf( /* translators: %s: Attributname. */ __( 'Das Attribut "%s" enthält keine gültige Option.', 'wp-agent-companion' ), $name ), array( 'status' => 400 ) );
		}
		if ( $is_global ) {
			$allowed = wpagent_wc_require_assign_terms( $attribute_name );
			if ( is_wp_error( $allowed ) ) {
				return $allowed;
			}
			$term_ids = array();
			foreach ( $options as $option ) {
				$term = ctype_digit( $option ) ? get_term( (int) $option, $attribute_name ) : get_term_by( 'slug', sanitize_title( $option ), $attribute_name );
				if ( ! $term || is_wp_error( $term ) ) {
					$term = get_term_by( 'name', $option, $attribute_name );
				}
				if ( ! $term || is_wp_error( $term ) ) {
					return new WP_Error( 'wpagent_bad_attributes', sprintf( /* translators: 1: Option, 2: Attribut-Taxonomie. */ __( 'Die Option "%1$s" existiert nicht in der globalen Attribut-Taxonomie "%2$s".', 'wp-agent-companion' ), $option, $attribute_name ), array( 'status' => 400 ) );
				}
				$term_ids[] = (int) $term->term_id;
			}
			$options = array_values( array_unique( $term_ids ) );
		}
		$attribute = new WC_Product_Attribute();
		$attribute->set_id( $is_global ? (int) wc_attribute_taxonomy_id_by_name( $attribute_name ) : 0 );
		$attribute->set_name( $attribute_name );
		$attribute->set_options( $options );
		$attribute->set_position( $position );
		$attribute->set_visible( (bool) ( $item['visible'] ?? true ) );
		$attribute->set_variation( (bool) ( $item['variation'] ?? false ) );
		$result[] = $attribute;
	}
	return $result;
}

/**
 * @return true|WP_Error
 */
function wpagent_wc_validate_sku( string $sku, int $current_id = 0 ) {
	if ( '' === $sku ) {
		return true;
	}
	$existing_id = wc_get_product_id_by_sku( $sku );
	if ( $existing_id > 0 && $existing_id !== $current_id ) {
		return new WP_Error( 'wpagent_sku_collision', sprintf( /* translators: 1: SKU, 2: Produkt-ID. */ __( 'Die SKU "%1$s" wird bereits von Produkt #%2$d verwendet.', 'wp-agent-companion' ), $sku, $existing_id ), array( 'status' => 400 ) );
	}
	return true;
}
