<?php
/** Guarded update ability for ACPT Lite WooCommerce product-data configuration. */

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

/** @return array<string,mixed> */
function wpagent_ability_update_acpt_woocommerce_product_data(): array {
	return array(
		'label'       => __( 'ACPT-Lite-WooCommerce-Product-Data aktualisieren', 'wp-agent-companion' ),
		'description' => __( 'Aktualisiert nur Standardwerte, Beschreibungen, Pflichtstatus und die UI-Sichtbarkeit einer bestehenden sicheren ACPT-Lite-2.0.11-Product-Data-Gruppe. Inhalt, Gruppen, Optionen und Feldstruktur bleiben unverändert.', 'wp-agent-companion' ),
		'category'    => 'wp-agent',
		'input_schema' => array(
			'type' => 'object',
			'properties' => array(
				'id' => array( 'type' => 'string', 'pattern' => '^[a-f0-9-]{36}$' ),
				'expected_hash' => array( 'type' => 'string', 'pattern' => '^[a-f0-9]{64}$' ),
				'show_in_ui' => array( 'type' => 'boolean' ),
				'fields' => array(
					'type' => 'array', 'maxItems' => WPAGENT_ACPT_PRODUCT_DATA_MAX_FIELDS,
					'items' => array(
						'type' => 'object',
						'properties' => array( 'id' => array( 'type' => 'string', 'pattern' => '^[a-f0-9-]{36}$' ), 'default_value' => array( 'type' => 'string', 'maxLength' => 200 ), 'description' => array( 'type' => 'string', 'maxLength' => 500 ), 'required' => array( 'type' => 'boolean' ) ),
						'required' => array( 'id' ), 'minProperties' => 2, 'additionalProperties' => false,
					),
				),
			),
			'required' => array( 'id', 'expected_hash', 'fields' ), 'minProperties' => 3, 'additionalProperties' => false,
		),
		'output_schema' => array(
			'type' => 'object', 'properties' => array( 'verified' => array( 'type' => 'boolean' ), 'changed' => array( 'type' => 'boolean' ), 'group' => wpagent_acpt_product_data_group_schema() ),
			'required' => array( 'verified', 'changed', 'group' ), 'additionalProperties' => false,
		),
		'execute_callback' => 'wpagent_exec_update_acpt_woocommerce_product_data',
		'permission_callback' => 'wpagent_perm_acpt_product_data',
		'meta' => array( 'show_in_rest' => true, 'annotations' => array( 'readonly' => false, 'destructive' => false, 'idempotent' => true ) ),
	);
}

/** @return true|WP_Error */
function wpagent_acpt_product_data_keys( array $input, array $allowed, array $required, string $label ) {
	if ( array() !== array_diff( array_keys( $input ), $allowed ) ) return new WP_Error( 'wpagent_acpt_product_data_invalid', sprintf( __( 'Unbekannte Eingabefelder in %s.', 'wp-agent-companion' ), $label ), array( 'status' => 400 ) );
	foreach ( $required as $key ) if ( ! array_key_exists( $key, $input ) ) return new WP_Error( 'wpagent_acpt_product_data_invalid', sprintf( __( 'Das Feld %s ist erforderlich.', 'wp-agent-companion' ), $key ), array( 'status' => 400 ) );
	return true;
}

/** @return array<string,mixed>|WP_Error */
function wpagent_acpt_product_data_validate_changes( array $input, array $state ) {
	$keys = wpagent_acpt_product_data_keys( $input, array( 'id', 'expected_hash', 'show_in_ui', 'fields' ), array( 'id', 'expected_hash', 'fields' ), 'Product Data' );
	if ( is_wp_error( $keys ) || ! wpagent_acpt_product_data_uuid( $input['id'] ?? null ) || ! is_string( $input['expected_hash'] ?? null ) || ! preg_match( '/^[a-f0-9]{64}$/D', $input['expected_hash'] ) || ! is_array( $input['fields'] ) || count( $input['fields'] ) > WPAGENT_ACPT_PRODUCT_DATA_MAX_FIELDS ) return new WP_Error( 'wpagent_acpt_product_data_invalid', __( 'Die Product-Data-Änderung ist nicht gültig.', 'wp-agent-companion' ), array( 'status' => 400 ) );
	if ( array_key_exists( 'show_in_ui', $input ) && ! is_bool( $input['show_in_ui'] ) ) return new WP_Error( 'wpagent_acpt_product_data_invalid', __( 'show_in_ui muss ein boolescher Wert sein.', 'wp-agent-companion' ), array( 'status' => 400 ) );
	$known = array();
	foreach ( $state['public']['fields'] as $field ) $known[ $field['id'] ] = $field;
	$changes = array();
	foreach ( $input['fields'] as $change ) {
		if ( ! is_array( $change ) || is_wp_error( wpagent_acpt_product_data_keys( $change, array( 'id', 'default_value', 'description', 'required' ), array( 'id' ), 'Feldänderung' ) ) ) return new WP_Error( 'wpagent_acpt_product_data_invalid', __( 'Eine Feldänderung ist nicht gültig.', 'wp-agent-companion' ), array( 'status' => 400 ) );
		$id = $change['id'] ?? null;
		if ( ! wpagent_acpt_product_data_uuid( $id ) || ! isset( $known[ $id ] ) || isset( $changes[ $id ] ) || count( $change ) < 2 ) return new WP_Error( 'wpagent_acpt_product_data_invalid', __( 'Eine Feldänderung enthält eine doppelte oder unbekannte ID.', 'wp-agent-companion' ), array( 'status' => 400 ) );
		$current = $known[ $id ];
		$next = array();
		if ( array_key_exists( 'default_value', $change ) ) {
			$value = wpagent_acpt_product_data_safe_text( $change['default_value'], 'Standardwert', 'Number' === $current['type'] ? 30 : 200 );
			if ( is_wp_error( $value ) || ( 'Number' === $current['type'] && ( ! preg_match( '/^-?(?:\d+|\d*\.\d+)$/D', $value ) || ! is_finite( (float) $value ) ) ) ) return new WP_Error( 'wpagent_acpt_product_data_invalid', __( 'Der Standardwert passt nicht zum ACPT-Lite-Feldtyp.', 'wp-agent-companion' ), array( 'status' => 400 ) );
			if ( 'Select' === $current['type'] && '' !== $value && ! in_array( $value, array_column( $current['options'], 'value' ), true ) ) return new WP_Error( 'wpagent_acpt_product_data_invalid', __( 'Der Select-Standardwert ist keine vorhandene Provideroption.', 'wp-agent-companion' ), array( 'status' => 400 ) );
			$next['default_value'] = $value;
		}
		if ( array_key_exists( 'description', $change ) ) {
			$description = wpagent_acpt_product_data_safe_text( $change['description'], 'Beschreibung', 500 );
			if ( is_wp_error( $description ) ) return $description;
			$next['description'] = $description;
		}
		if ( array_key_exists( 'required', $change ) ) {
			if ( ! is_bool( $change['required'] ) ) return new WP_Error( 'wpagent_acpt_product_data_invalid', __( 'required muss ein boolescher Wert sein.', 'wp-agent-companion' ), array( 'status' => 400 ) );
			$next['required'] = $change['required'];
		}
		$changes[ $id ] = $next;
	}
	if ( array() === $changes && ! array_key_exists( 'show_in_ui', $input ) ) return new WP_Error( 'wpagent_acpt_product_data_invalid', __( 'Die Product-Data-Änderung enthält keine erlaubte Aktualisierung.', 'wp-agent-companion' ), array( 'status' => 400 ) );
	return array( 'show_in_ui' => $input['show_in_ui'] ?? $state['public']['show_in_ui'], 'fields' => $changes );
}

function wpagent_acpt_product_data_changes_state( array $state, array $changes ): bool {
	if ( $state['public']['show_in_ui'] !== $changes['show_in_ui'] ) return true;
	foreach ( $state['public']['fields'] as $field ) {
		foreach ( $changes['fields'][ $field['id'] ] ?? array() as $key => $value ) if ( $field[ $key ] !== $value ) return true;
	}
	return false;
}

/** @return object */
function wpagent_acpt_product_data_native_model( array $state, array $changes, array $provider ) {
	$raw = $state['raw'];
	$group_row = $raw['group'];
	$model_class = $provider['model'];
	$field_class = $provider['field_model'];
	$option_class = $provider['option_model'];
	$model = new $model_class( $group_row['id'], $group_row['product_data_name'], json_decode( $group_row['icon'], true ), json_decode( $group_row['visibility'], true ), $changes['show_in_ui'] );
	$model->setContent( $group_row['content'] );
	$options = array();
	foreach ( $raw['options'] as $option ) $options[ $option['product_data_field_id'] ][] = $option;
	foreach ( $raw['fields'] as $field_row ) {
		$change = $changes['fields'][ $field_row['id'] ] ?? array();
		$field = new $field_class( $field_row['id'], $model, $field_row['field_name'], $field_row['field_type'], array_key_exists( 'required', $change ) ? $change['required'] : ( '1' === (string) $field_row['required'] ), (int) $field_row['sort'], $change['default_value'] ?? $field_row['field_default_value'], $change['description'] ?? $field_row['field_description'] );
		foreach ( $options[ $field_row['id'] ] ?? array() as $option_row ) $field->addOption( new $option_class( $option_row['id'], $field, $option_row['option_label'], $option_row['option_value'], (int) $option_row['sort'] ) );
		$model->addField( $field );
	}
	return $model;
}

function wpagent_acpt_product_data_preserved( array $before, array $after, array $changes ): bool {
	if ( ! is_array( $after['group'] ?? null ) || ! is_array( $after['fields'] ?? null ) || ! is_array( $after['options'] ?? null ) || count( $before['fields'] ) !== count( $after['fields'] ) || count( $before['options'] ) !== count( $after['options'] ) ) return false;
	foreach ( $before['group'] as $key => $value ) if ( 'show_in_ui' !== $key && ( ! array_key_exists( $key, $after['group'] ) || $after['group'][ $key ] !== $value ) ) return false;
	$after_fields = array();
	foreach ( $after['fields'] as $field ) if ( is_array( $field ) && isset( $field['id'] ) ) $after_fields[ $field['id'] ] = $field;
	foreach ( $before['fields'] as $field ) {
		$id = $field['id'] ?? '';
		if ( ! isset( $after_fields[ $id ] ) ) return false;
		foreach ( $field as $key => $value ) {
			$change = $changes['fields'][ $id ] ?? array();
			$allowed = ( 'field_default_value' === $key && array_key_exists( 'default_value', $change ) )
				|| ( 'field_description' === $key && array_key_exists( 'description', $change ) )
				|| ( 'required' === $key && array_key_exists( 'required', $change ) );
			if ( $allowed ) continue;
			if ( ! array_key_exists( $key, $after_fields[ $id ] ) || $after_fields[ $id ][ $key ] !== $value ) return false;
		}
	}
	return wpagent_acpt_product_data_canonicalize( $before['options'] ) === wpagent_acpt_product_data_canonicalize( $after['options'] );
}

/** @return true|WP_Error */
function wpagent_acpt_product_data_verify_tab( object $model, array $provider ) {
	try {
		$generator = new $provider['generator']( array( $model ) );
		$tabs = $generator->generateWooCommerceProductDataTabs( array() );
		$slug = $model->getSluggedName();
		$expected = array( 'label' => __( $model->getName(), 'woocommerce' ), 'target' => $slug . '_options', 'priority' => 45, 'class' => $model->getVisibility() );
		if ( ! is_array( $tabs ) || ! isset( $tabs[ $slug ] ) || $tabs[ $slug ] !== $expected ) throw new UnexpectedValueException( 'ACPT tab mismatch.' );
	} catch ( Throwable $error ) {
		return new WP_Error( 'wpagent_acpt_product_data_tab', __( 'Der native ACPT-Lite-WooCommerce-Tab konnte nicht exakt bestätigt werden.', 'wp-agent-companion' ), array( 'status' => 409 ) );
	}
	return true;
}

/** @return array<string,mixed>|WP_Error */
function wpagent_exec_update_acpt_woocommerce_product_data( $input ) {
	$input = is_array( $input ) ? $input : array();
	$id = $input['id'] ?? '';
	if ( ! wpagent_acpt_product_data_uuid( $id ) ) return new WP_Error( 'wpagent_acpt_product_data_invalid', __( 'Die Product-Data-ID ist nicht gültig.', 'wp-agent-companion' ), array( 'status' => 400 ) );
	$capability = wpagent_acpt_product_data_require_capability();
	if ( is_wp_error( $capability ) ) return $capability;
	$provider = wpagent_acpt_product_data_provider();
	if ( is_wp_error( $provider ) ) return $provider;
	$before = wpagent_acpt_product_data_state( $id, $provider );
	if ( is_wp_error( $before ) ) return $before;
	if ( ! hash_equals( $before['state_hash'], (string) ( $input['expected_hash'] ?? '' ) ) ) return new WP_Error( 'wpagent_acpt_product_data_conflict', __( 'Die ACPT-Lite-Product-Data-Gruppe wurde seit dem Lesen geändert.', 'wp-agent-companion' ), array( 'status' => 409 ) );
	$changes = wpagent_acpt_product_data_validate_changes( $input, $before );
	if ( is_wp_error( $changes ) ) return $changes;
	if ( ! wpagent_acpt_product_data_changes_state( $before, $changes ) ) return array( 'verified' => true, 'changed' => false, 'group' => array_merge( $before['public'], array( 'provider_version' => $provider['version'], 'state_hash' => $before['state_hash'] ) ) );
	$lock = wpagent_acpt_product_data_lock_name( $id );
	$token = wpagent_companion_acquire_option_lock( $lock, WPAGENT_ACPT_PRODUCT_DATA_LOCK_TTL );
	if ( ! is_string( $token ) ) return new WP_Error( 'wpagent_acpt_product_data_locked', __( 'Eine andere ACPT-Lite-Product-Data-Aktualisierung läuft bereits.', 'wp-agent-companion' ), array( 'status' => 409 ) );
	$started = false;
	try {
		$capability = wpagent_acpt_product_data_require_capability();
		if ( is_wp_error( $capability ) ) return $capability;
		$provider = wpagent_acpt_product_data_provider();
		if ( is_wp_error( $provider ) ) return $provider;
		$current = wpagent_acpt_product_data_state( $id, $provider );
		if ( is_wp_error( $current ) ) return $current;
		if ( ! hash_equals( $current['state_hash'], (string) $input['expected_hash'] ) ) return new WP_Error( 'wpagent_acpt_product_data_conflict', __( 'Die ACPT-Lite-Product-Data-Gruppe wurde vor dem Write verändert.', 'wp-agent-companion' ), array( 'status' => 409 ) );
		if ( ! wpagent_companion_refresh_option_lock( $lock, $token, WPAGENT_ACPT_PRODUCT_DATA_LOCK_TTL ) ) return new WP_Error( 'wpagent_acpt_product_data_lock', __( 'Die ACPT-Lite-Product-Data-Sperre wurde vor dem Write verloren.', 'wp-agent-companion' ), array( 'status' => 409 ) );
		$model = wpagent_acpt_product_data_native_model( $current, $changes, $provider );
		$tab = wpagent_acpt_product_data_verify_tab( $model, $provider );
		if ( is_wp_error( $tab ) ) return $tab;
		$started = true;
		$provider['repository']::save( $model );
		do_action( 'wpagent_acpt_product_data_after_provider_write', $id, $lock, $token );
		if ( ! wpagent_acpt_product_data_can_manage() ) throw new RuntimeException( 'Capability lost after ACPT Product Data write.' );
		$post_provider = wpagent_acpt_product_data_provider();
		if ( is_wp_error( $post_provider ) ) throw new RuntimeException( 'ACPT Product Data runtime changed after write.' );
		$provider = $post_provider;
		if ( ! wpagent_companion_refresh_option_lock( $lock, $token, WPAGENT_ACPT_PRODUCT_DATA_LOCK_TTL ) ) throw new RuntimeException( 'ACPT Product Data lock lost after write.' );
		$cache = $provider['database']::flushCache();
		if ( false === $cache ) throw new RuntimeException( 'ACPT Product Data cache flush failed.' );
		$after = wpagent_acpt_product_data_state( $id, $provider );
		if ( is_wp_error( $after ) || ! wpagent_acpt_product_data_preserved( $current['raw'], $after['raw'], $changes ) || $after['public']['show_in_ui'] !== $changes['show_in_ui'] || ! wpagent_acpt_product_data_can_manage() || ! wpagent_companion_refresh_option_lock( $lock, $token, WPAGENT_ACPT_PRODUCT_DATA_LOCK_TTL ) ) throw new RuntimeException( 'ACPT Product Data readback mismatch.' );
		foreach ( $changes['fields'] as $field_id => $change ) {
			$after_fields = array_column( $after['public']['fields'], null, 'id' );
			if ( ! isset( $after_fields[ $field_id ] ) ) throw new RuntimeException( 'ACPT Product Data field drift.' );
			foreach ( $change as $key => $value ) if ( $after_fields[ $field_id ][ $key ] !== $value ) throw new RuntimeException( 'ACPT Product Data field readback mismatch.' );
		}
		$tab = wpagent_acpt_product_data_verify_tab( $after['model'], $provider );
		if ( is_wp_error( $tab ) ) throw new RuntimeException( 'ACPT Product Data tab drift.' );
		return array( 'verified' => true, 'changed' => true, 'group' => array_merge( $after['public'], array( 'provider_version' => $provider['version'], 'state_hash' => $after['state_hash'] ) ) );
	} catch ( Throwable $error ) {
		$after = wpagent_acpt_product_data_state( $id, is_array( $provider ) ? $provider : array() );
		return $started ? wpagent_acpt_product_data_recovery_error( __( 'Der ACPT-Lite-Product-Data-Write wurde begonnen, aber nicht vollständig bestätigt. Es erfolgt keine automatische Folgemutation oder Rücknahme.', 'wp-agent-companion' ), is_array( $after ) ? $after : null ) : new WP_Error( 'wpagent_acpt_product_data_write', __( 'Die ACPT-Lite-Product-Data-Gruppe konnte nicht sicher aktualisiert werden.', 'wp-agent-companion' ), array( 'status' => 500 ) );
	} finally {
		wpagent_companion_release_option_lock( $lock, $token );
	}
}
