<?php
/** Shared validation and provider state for bounded ACPT Lite meta groups. */

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

define( 'WPAGENT_ACPT_META_MAX_GROUPS', 500 );
define( 'WPAGENT_ACPT_META_MAX_BOXES', 10 );
define( 'WPAGENT_ACPT_META_MAX_FIELDS', 20 );
define( 'WPAGENT_ACPT_META_LOCK_TTL', 300 );
define( 'WPAGENT_ACPT_META_AUDITED_ABSENCE_WARNING_VERSION', '2.0.11' );

function wpagent_acpt_meta_has_audited_absence_warning_contract( string $provider_version ): bool {
	return hash_equals( WPAGENT_ACPT_META_AUDITED_ABSENCE_WARNING_VERSION, $provider_version );
}

/** @return 'missing_field'|'missing_box'|'' */
function wpagent_acpt_meta_audited_absence_warning_kind( string $provider_version, int $severity, string $message, string $file, int $line ): string {
	if ( ! wpagent_acpt_meta_has_audited_absence_warning_contract( $provider_version ) || E_WARNING !== $severity ) {
		return '';
	}
	$provider_file = str_ends_with( wp_normalize_path( $file ), '/src/Core/Repository/MetaRepository.php' );
	if ( $provider_file && 963 === $line && 'Undefined array key 0' === $message ) {
		return 'missing_field';
	}
	if ( $provider_file && 964 === $line && 'Attempt to read property "meta_box_id" on null' === $message ) {
		return 'missing_box';
	}
	return '';
}

/** @return array<string,string>|WP_Error */
function wpagent_acpt_meta_provider() {
	global $wpdb;
	$engine = defined( 'DB_ENGINE' ) ? strtolower( (string) DB_ENGINE ) : ( false !== stripos( get_class( $wpdb ), 'sqlite' ) ? 'sqlite' : 'mysql' );
	if ( 'sqlite' === $engine || ! isset( $wpdb->dbh ) || ! $wpdb->dbh instanceof mysqli ) {
		return new WP_Error( 'wpagent_acpt_meta_database', __( 'ACPT Lite unterstützt diesen Metagruppenpfad nur auf MySQL oder MariaDB.', 'wp-agent-companion' ), array( 'status' => 409 ) );
	}
	$provider = array(
		'version'    => defined( 'ACPT_LITE_PLUGIN_VERSION' ) ? (string) ACPT_LITE_PLUGIN_VERSION : '',
		'repository' => 'ACPT_Lite\\Core\\Repository\\MetaRepository',
		'command'    => 'ACPT_Lite\\Core\\CQRS\\Command\\SaveMetaGroupCommand',
		'database'   => 'ACPT_Lite\\Includes\\ACPT_Lite_DB',
	);
	if ( '' === $provider['version'] || version_compare( $provider['version'], '2.0.0', '<' ) || version_compare( $provider['version'], '3.0.0', '>=' ) ) {
		return new WP_Error( 'wpagent_acpt_meta_missing', __( 'ACPT Lite 2.x ist nicht aktiv.', 'wp-agent-companion' ), array( 'status' => 409 ) );
	}
	if ( ! class_exists( $provider['repository'] ) || ! class_exists( $provider['command'] ) || ! class_exists( $provider['database'] ) || ! is_callable( array( $provider['database'], 'flushCache' ) ) || ! function_exists( 'save_acpt_meta_field_value' ) ) {
		return new WP_Error( 'wpagent_acpt_meta_api', __( 'Die benötigte native ACPT-Lite-Metadaten-API ist nicht verfügbar.', 'wp-agent-companion' ), array( 'status' => 409 ) );
	}
	foreach ( array( 'get', 'count', 'deleteMetaGroup' ) as $method ) {
		if ( ! is_callable( array( $provider['repository'], $method ) ) ) {
			return new WP_Error( 'wpagent_acpt_meta_api', __( 'Die benötigte native ACPT-Lite-Metadaten-API ist nicht verfügbar.', 'wp-agent-companion' ), array( 'status' => 409 ) );
		}
	}
	return $provider;
}

/** @return string|WP_Error */
function wpagent_acpt_meta_identifier( $value, string $label ) {
	$name = is_string( $value ) ? $value : '';
	if ( ! preg_match( '/^[a-z][a-z0-9_]{2,39}$/D', $name ) ) {
		/* translators: %s: input field name. */
		return new WP_Error( 'wpagent_acpt_meta_identifier', sprintf( __( '%s muss 3 bis 40 Zeichen lang sein, mit a-z beginnen und darf nur a-z, 0-9 und Unterstriche enthalten.', 'wp-agent-companion' ), $label ), array( 'status' => 400 ) );
	}
	return $name;
}

function wpagent_acpt_meta_uuid_is_valid( $value ): bool {
	return is_string( $value ) && 1 === preg_match( '/^[a-f0-9]{8}-[a-f0-9]{4}-[1-5][a-f0-9]{3}-[89ab][a-f0-9]{3}-[a-f0-9]{12}$/Di', $value );
}

/** @return string|WP_Error */
function wpagent_acpt_meta_text( $value, string $label, int $max, bool $allow_empty = false ) {
	$text = is_string( $value ) ? trim( $value ) : '';
	$length = function_exists( 'mb_strlen' ) ? mb_strlen( $text, 'UTF-8' ) : strlen( $text );
	if ( ( ! $allow_empty && '' === $text ) || $length > $max || sanitize_text_field( $text ) !== $text ) {
		/* translators: 1: input field name, 2: maximum number of characters. */
		return new WP_Error( 'wpagent_acpt_meta_text', sprintf( __( '%1$s muss reiner Text mit höchstens %2$d Zeichen sein.', 'wp-agent-companion' ), $label, $max ), array( 'status' => 400 ) );
	}
	return $text;
}

/** @return array<string,string>|WP_Error */
function wpagent_acpt_meta_target( $value ) {
	if ( ! is_array( $value ) || ! is_string( $value['type'] ?? null ) || ! array_key_exists( 'name', $value ) ) {
		return new WP_Error( 'wpagent_acpt_meta_target', __( 'target muss genau einen unterstützten Zieltyp und Namen enthalten.', 'wp-agent-companion' ), array( 'status' => 400 ) );
	}
	$type = $value['type'];
	$name = is_string( $value['name'] ) ? $value['name'] : '';
	if ( 'post_type' === $type ) {
		if ( ! preg_match( '/^[a-z0-9][a-z0-9_-]{0,19}$/D', $name ) || ! post_type_exists( $name ) ) {
			return new WP_Error( 'wpagent_acpt_meta_target', __( 'Das Ziel muss ein registrierter Inhaltstyp sein.', 'wp-agent-companion' ), array( 'status' => 400 ) );
		}
	} elseif ( 'taxonomy' === $type ) {
		if ( ! preg_match( '/^[a-z0-9][a-z0-9_-]{0,31}$/D', $name ) || ! taxonomy_exists( $name ) ) {
			return new WP_Error( 'wpagent_acpt_meta_target', __( 'Das Ziel muss eine registrierte Taxonomie sein.', 'wp-agent-companion' ), array( 'status' => 400 ) );
		}
	} elseif ( in_array( $type, array( 'user', 'comment' ), true ) ) {
		if ( '' !== $name ) {
			return new WP_Error( 'wpagent_acpt_meta_target', __( 'Benutzer- und Kommentarziele verwenden einen leeren Namen.', 'wp-agent-companion' ), array( 'status' => 400 ) );
		}
	} else {
		return new WP_Error( 'wpagent_acpt_meta_target', __( 'ACPT-Lite-Optionsseiten und andere Zieltypen sind in diesem sicheren Pfad nicht verfügbar.', 'wp-agent-companion' ), array( 'status' => 400 ) );
	}
	return array( 'type' => $type, 'name' => $name );
}

/** @return array<string,mixed> */
function wpagent_acpt_meta_belong_from_target( array $target, string $id = '' ): array {
	$map = array( 'post_type' => 'customPostType', 'taxonomy' => 'taxonomy', 'user' => 'user', 'comment' => 'comment' );
	$belong = array(
		'belongsTo' => $map[ $target['type'] ],
		'logic'     => '',
		'find'      => in_array( $target['type'], array( 'post_type', 'taxonomy' ), true ) ? $target['name'] : null,
	);
	if ( in_array( $target['type'], array( 'post_type', 'taxonomy' ), true ) ) {
		$belong['operator'] = '=';
	}
	if ( '' !== $id ) {
		$belong['id'] = $id;
	}
	return $belong;
}

/** @return array<string,string>|WP_Error */
function wpagent_acpt_meta_target_from_belongs( array $belongs ) {
	if ( 1 !== count( $belongs ) || ! is_object( $belongs[0] ) ) {
		return new WP_Error( 'wpagent_acpt_meta_shape', __( 'Die ACPT-Lite-Metagruppe hat keine eindeutig unterstützte Zielbindung.', 'wp-agent-companion' ), array( 'status' => 409 ) );
	}
	$belong = $belongs[0];
	try {
		$type = $belong->getBelongsTo();
		$find = $belong->getFind();
		$operator = $belong->getOperator();
		$logic = $belong->getLogic();
	} catch ( Throwable $error ) {
		return new WP_Error( 'wpagent_acpt_meta_shape', __( 'Die ACPT-Lite-Zielbindung ist beschädigt.', 'wp-agent-companion' ), array( 'status' => 500 ) );
	}
	$map = array( 'customPostType' => 'post_type', 'taxonomy' => 'taxonomy', 'user' => 'user', 'comment' => 'comment' );
	if ( ! isset( $map[ $type ] ) || ! in_array( $logic, array( null, '' ), true ) ) {
		return new WP_Error( 'wpagent_acpt_meta_shape', __( 'Die ACPT-Lite-Metagruppe verwendet eine nicht unterstützte Zielbedingung.', 'wp-agent-companion' ), array( 'status' => 409 ) );
	}
	$public_type = $map[ $type ];
	if ( in_array( $public_type, array( 'post_type', 'taxonomy' ), true ) ) {
		if ( '=' !== $operator || ! is_string( $find ) || '' === $find || false !== strpos( $find, ',' ) ) {
			return new WP_Error( 'wpagent_acpt_meta_shape', __( 'Die ACPT-Lite-Metagruppe verwendet eine nicht unterstützte Zielbedingung.', 'wp-agent-companion' ), array( 'status' => 409 ) );
		}
		return array( 'type' => $public_type, 'name' => $find );
	}
	if ( ! in_array( $operator, array( null, '' ), true ) || ! in_array( $find, array( null, '' ), true ) ) {
		return new WP_Error( 'wpagent_acpt_meta_shape', __( 'Die ACPT-Lite-Metagruppe verwendet eine nicht unterstützte Zielbedingung.', 'wp-agent-companion' ), array( 'status' => 409 ) );
	}
	return array( 'type' => $public_type, 'name' => '' );
}

/** @return array<int,array<string,mixed>>|WP_Error */
function wpagent_acpt_meta_validate_boxes( $value ) {
	if ( ! is_array( $value ) || array() === $value || ! array_is_list( $value ) || count( $value ) > WPAGENT_ACPT_META_MAX_BOXES ) {
		return new WP_Error( 'wpagent_acpt_meta_boxes', __( 'boxes muss 1 bis 10 Metaboxen enthalten.', 'wp-agent-companion' ), array( 'status' => 400 ) );
	}
	$boxes = array();
	$box_names = array();
	$field_names = array();
	$field_count = 0;
	$type_map = array( 'date' => 'Date', 'email' => 'Email', 'select' => 'Select', 'text' => 'Text', 'textarea' => 'Textarea' );
	foreach ( $value as $box_index => $raw_box ) {
		if ( ! is_array( $raw_box ) || ! array_is_list( $raw_box['fields'] ?? null ) || array() === $raw_box['fields'] ) {
			return new WP_Error( 'wpagent_acpt_meta_boxes', __( 'Jede Metabox benötigt eine nicht leere fields-Liste.', 'wp-agent-companion' ), array( 'status' => 400 ) );
		}
		$name = wpagent_acpt_meta_identifier( $raw_box['name'] ?? '', 'box.name' );
		$label = wpagent_acpt_meta_text( $raw_box['label'] ?? '', 'box.label', 100 );
		if ( is_wp_error( $name ) || is_wp_error( $label ) ) return is_wp_error( $name ) ? $name : $label;
		if ( isset( $box_names[ $name ] ) ) return new WP_Error( 'wpagent_acpt_meta_duplicate', __( 'Metabox-Namen dürfen nicht doppelt vorkommen.', 'wp-agent-companion' ), array( 'status' => 400 ) );
		$box_names[ $name ] = true;
		$fields = array();
		foreach ( $raw_box['fields'] as $field_index => $raw_field ) {
			if ( ! is_array( $raw_field ) || ++$field_count > WPAGENT_ACPT_META_MAX_FIELDS ) {
				return new WP_Error( 'wpagent_acpt_meta_fields', __( 'Eine Metagruppe darf höchstens 20 einfache Felder enthalten.', 'wp-agent-companion' ), array( 'status' => 400 ) );
			}
			$field_name = wpagent_acpt_meta_identifier( $raw_field['name'] ?? '', 'field.name' );
			$field_label = wpagent_acpt_meta_text( $raw_field['label'] ?? '', 'field.label', 100 );
			$type = is_string( $raw_field['type'] ?? null ) ? $raw_field['type'] : '';
			$required = $raw_field['required'] ?? false;
			if ( is_wp_error( $field_name ) || is_wp_error( $field_label ) ) return is_wp_error( $field_name ) ? $field_name : $field_label;
			if ( ! isset( $type_map[ $type ] ) || ! is_bool( $required ) ) return new WP_Error( 'wpagent_acpt_meta_field', __( 'Felder erlauben nur die ACPT-Lite-Typen date, email, select, text und textarea sowie einen booleschen required-Wert.', 'wp-agent-companion' ), array( 'status' => 400 ) );
			if ( isset( $field_names[ $field_name ] ) ) return new WP_Error( 'wpagent_acpt_meta_duplicate', __( 'ACPT-Feldnamen dürfen innerhalb einer Metagruppe nicht doppelt vorkommen.', 'wp-agent-companion' ), array( 'status' => 400 ) );
			$field_names[ $field_name ] = true;
			$options = array();
			if ( 'select' === $type ) {
				$raw_options = $raw_field['options'] ?? null;
				if ( ! is_array( $raw_options ) || ! array_is_list( $raw_options ) || array() === $raw_options || count( $raw_options ) > 20 ) {
					return new WP_Error( 'wpagent_acpt_meta_options', __( 'Ein Select-Feld benötigt 1 bis 20 Optionen.', 'wp-agent-companion' ), array( 'status' => 400 ) );
				}
				$option_values = array();
				foreach ( $raw_options as $raw_option ) {
					if ( ! is_array( $raw_option ) ) return new WP_Error( 'wpagent_acpt_meta_options', __( 'Select-Optionen sind ungültig.', 'wp-agent-companion' ), array( 'status' => 400 ) );
					$option_label = wpagent_acpt_meta_text( $raw_option['label'] ?? '', 'option.label', 100 );
					$option_value = wpagent_acpt_meta_text( $raw_option['value'] ?? '', 'option.value', 100 );
					if ( is_wp_error( $option_label ) || is_wp_error( $option_value ) ) return is_wp_error( $option_label ) ? $option_label : $option_value;
					if ( isset( $option_values[ $option_value ] ) ) return new WP_Error( 'wpagent_acpt_meta_duplicate', __( 'Select-Werte dürfen nicht doppelt vorkommen.', 'wp-agent-companion' ), array( 'status' => 400 ) );
					$option_values[ $option_value ] = true;
					$options[] = array( 'label' => $option_label, 'value' => $option_value );
				}
			} elseif ( array_key_exists( 'options', $raw_field ) && array() !== $raw_field['options'] ) {
				return new WP_Error( 'wpagent_acpt_meta_options', __( 'Nur Select-Felder dürfen Optionen enthalten.', 'wp-agent-companion' ), array( 'status' => 400 ) );
			}
			$fields[] = array( 'name' => $field_name, 'label' => $field_label, 'type' => $type, 'required' => $required, 'options' => $options );
		}
		$boxes[] = array( 'name' => $name, 'label' => $label, 'fields' => $fields );
	}
	return $boxes;
}

/** @return array<string,mixed>|WP_Error */
function wpagent_acpt_meta_validate_definition( array $input, bool $create ) {
	$name = wpagent_acpt_meta_identifier( $input['name'] ?? '', 'name' );
	if ( is_wp_error( $name ) ) return $name;
	$definition = array( 'name' => $name );
	foreach ( array( 'label' => 100 ) as $field => $max ) {
		if ( $create || array_key_exists( $field, $input ) ) {
			$value = wpagent_acpt_meta_text( $input[ $field ] ?? '', $field, $max );
			if ( is_wp_error( $value ) ) return $value;
			$definition[ $field ] = $value;
		}
	}
	if ( $create || array_key_exists( 'target', $input ) ) {
		$target = wpagent_acpt_meta_target( $input['target'] ?? null );
		if ( is_wp_error( $target ) ) return $target;
		$definition['target'] = $target;
	}
	foreach ( array( 'context' => array( 'normal', 'advanced', 'side' ), 'priority' => array( 'default', 'high', 'low' ) ) as $field => $allowed ) {
		if ( $create || array_key_exists( $field, $input ) ) {
			$value = is_string( $input[ $field ] ?? null ) ? $input[ $field ] : '';
			if ( ! in_array( $value, $allowed, true ) ) return new WP_Error( 'wpagent_acpt_meta_setting', __( 'context oder priority enthält einen nicht freigegebenen Wert.', 'wp-agent-companion' ), array( 'status' => 400 ) );
			$definition[ $field ] = $value;
		}
	}
	if ( $create || array_key_exists( 'boxes', $input ) ) {
		$boxes = wpagent_acpt_meta_validate_boxes( $input['boxes'] ?? null );
		if ( is_wp_error( $boxes ) ) return $boxes;
		$definition['boxes'] = $boxes;
	}
	return $definition;
}

/** @return array<string,mixed>|WP_Error */
function wpagent_acpt_meta_group_state_from_model( object $model, string $provider_version ) {
	$type_map = array( 'Date' => 'date', 'Email' => 'email', 'Select' => 'select', 'Text' => 'text', 'Textarea' => 'textarea' );
	try {
		$id = $model->getId();
		$name = $model->getName();
		$label = $model->getLabel();
		$display = $model->getDisplay();
		$context = $model->getContext();
		$priority = $model->getPriority();
		$belongs = $model->getBelongs();
		$provider_boxes = $model->getBoxes();
	} catch ( Throwable $error ) {
		return new WP_Error( 'wpagent_acpt_meta_shape', __( 'ACPT Lite lieferte eine beschädigte Metagruppe.', 'wp-agent-companion' ), array( 'status' => 500 ) );
	}
	$valid_name = wpagent_acpt_meta_identifier( $name, 'name' );
	$valid_label = wpagent_acpt_meta_text( $label, 'label', 100 );
	if ( ! wpagent_acpt_meta_uuid_is_valid( $id ) || is_wp_error( $valid_name ) || $valid_name !== $name || is_wp_error( $valid_label ) || $valid_label !== $label || 'standard' !== $display || ! in_array( $context, array( 'normal', 'advanced', 'side' ), true ) || ! in_array( $priority, array( 'default', 'high', 'low' ), true ) || ! is_array( $belongs ) || ! is_array( $provider_boxes ) || array() === $provider_boxes || count( $provider_boxes ) > WPAGENT_ACPT_META_MAX_BOXES ) {
		return new WP_Error( 'wpagent_acpt_meta_shape', __( 'Die ACPT-Lite-Metagruppe liegt außerhalb des sicheren Strukturvertrags.', 'wp-agent-companion' ), array( 'status' => 409 ) );
	}
	$target = wpagent_acpt_meta_target_from_belongs( $belongs );
	if ( is_wp_error( $target ) ) return $target;
	try { $binding_id = $belongs[0]->getId(); } catch ( Throwable $error ) { $binding_id = ''; }
	if ( ! wpagent_acpt_meta_uuid_is_valid( $binding_id ) ) return new WP_Error( 'wpagent_acpt_meta_shape', __( 'Die ACPT-Lite-Zielbindung besitzt keine gültige ID.', 'wp-agent-companion' ), array( 'status' => 500 ) );
	$validated_target = wpagent_acpt_meta_target( $target );
	if ( is_wp_error( $validated_target ) || $validated_target !== $target ) return new WP_Error( 'wpagent_acpt_meta_shape', __( 'Die ACPT-Lite-Zielbindung ist nicht mehr eindeutig registriert.', 'wp-agent-companion' ), array( 'status' => 409 ) );
	$boxes = array();
	$box_names = array();
	$field_names = array();
	$field_count = 0;
	foreach ( $provider_boxes as $box_index => $box ) {
		try {
			$box_id = $box->getId(); $box_name = $box->getName(); $box_label = $box->getLabel(); $box_sort = $box->getSort(); $provider_fields = $box->getFields();
		} catch ( Throwable $error ) {
			return new WP_Error( 'wpagent_acpt_meta_shape', __( 'Eine ACPT-Lite-Metabox ist beschädigt.', 'wp-agent-companion' ), array( 'status' => 500 ) );
		}
		$valid_box_name = wpagent_acpt_meta_identifier( $box_name, 'box.name' );
		$valid_box_label = wpagent_acpt_meta_text( $box_label, 'box.label', 100 );
		if ( ! wpagent_acpt_meta_uuid_is_valid( $box_id ) || is_wp_error( $valid_box_name ) || $valid_box_name !== $box_name || isset( $box_names[ $box_name ] ) || is_wp_error( $valid_box_label ) || $valid_box_label !== $box_label || ! is_int( $box_sort ) || $box_sort !== $box_index + 1 || ! is_array( $provider_fields ) || array() === $provider_fields ) return new WP_Error( 'wpagent_acpt_meta_shape', __( 'Eine ACPT-Lite-Metabox liegt außerhalb des sicheren Strukturvertrags.', 'wp-agent-companion' ), array( 'status' => 409 ) );
		$box_names[ $box_name ] = true;
		$fields = array();
		foreach ( $provider_fields as $field_index => $field ) {
			if ( ++$field_count > WPAGENT_ACPT_META_MAX_FIELDS ) return new WP_Error( 'wpagent_acpt_meta_shape', __( 'Die ACPT-Lite-Metagruppe enthält zu viele Felder.', 'wp-agent-companion' ), array( 'status' => 409 ) );
			try {
				$field_id = $field->getId(); $field_name = $field->getName(); $field_label = $field->getLabel(); $provider_type = $field->getType(); $required = $field->isRequired(); $field_sort = $field->getSort(); $options_raw = $field->getOptions();
				$default = $field->getDefaultValue(); $description = $field->getDescription(); $archive = $field->isShowInArchive(); $quick = $field->isForQuickEdit(); $filterable = $field->isFilterableInAdmin();
			} catch ( Throwable $error ) {
				return new WP_Error( 'wpagent_acpt_meta_shape', __( 'Ein ACPT-Lite-Metafeld ist beschädigt.', 'wp-agent-companion' ), array( 'status' => 500 ) );
			}
			$valid_field_name = wpagent_acpt_meta_identifier( $field_name, 'field.name' );
			$valid_field_label = wpagent_acpt_meta_text( $field_label, 'field.label', 100 );
			if ( ! isset( $type_map[ $provider_type ] ) || ! wpagent_acpt_meta_uuid_is_valid( $field_id ) || is_wp_error( $valid_field_name ) || $valid_field_name !== $field_name || isset( $field_names[ $field_name ] ) || is_wp_error( $valid_field_label ) || $valid_field_label !== $field_label || ! is_bool( $required ) || ! is_int( $field_sort ) || $field_sort !== $field_index + 1 || ! is_array( $options_raw ) || count( $options_raw ) > 20 || ! in_array( $default, array( null, '' ), true ) || ! in_array( $description, array( null, '' ), true ) || $archive || $quick || $filterable ) {
				return new WP_Error( 'wpagent_acpt_meta_shape', __( 'Ein ACPT-Lite-Metafeld liegt außerhalb des sicheren Strukturvertrags.', 'wp-agent-companion' ), array( 'status' => 409 ) );
			}
			$field_names[ $field_name ] = true;
			$options = array();
			$option_values = array();
			foreach ( $options_raw as $option_index => $option ) {
				try { $option_id = $option->getId(); $option_label = $option->getLabel(); $option_value = $option->getValue(); $option_sort = $option->getSort(); $option_default = $option->isDefault(); } catch ( Throwable $error ) { return new WP_Error( 'wpagent_acpt_meta_shape', __( 'Eine ACPT-Lite-Select-Option ist beschädigt.', 'wp-agent-companion' ), array( 'status' => 500 ) ); }
				$valid_option_label = wpagent_acpt_meta_text( $option_label, 'option.label', 100 );
				$valid_option_value = wpagent_acpt_meta_text( $option_value, 'option.value', 100 );
				if ( 'Select' !== $provider_type || ! wpagent_acpt_meta_uuid_is_valid( $option_id ) || is_wp_error( $valid_option_label ) || $valid_option_label !== $option_label || is_wp_error( $valid_option_value ) || $valid_option_value !== $option_value || isset( $option_values[ $option_value ] ) || ! is_int( $option_sort ) || $option_sort !== $option_index + 1 || $option_default ) return new WP_Error( 'wpagent_acpt_meta_shape', __( 'Eine ACPT-Lite-Select-Option liegt außerhalb des sicheren Strukturvertrags.', 'wp-agent-companion' ), array( 'status' => 409 ) );
				$option_values[ $option_value ] = true;
				$options[] = array( 'id' => $option_id, 'label' => $option_label, 'value' => $option_value, 'sort' => $option_sort );
			}
			if ( 'Select' === $provider_type && array() === $options ) return new WP_Error( 'wpagent_acpt_meta_shape', __( 'Ein ACPT-Lite-Select-Feld benötigt Optionen.', 'wp-agent-companion' ), array( 'status' => 409 ) );
			if ( 'Select' !== $provider_type && array() !== $options ) return new WP_Error( 'wpagent_acpt_meta_shape', __( 'Nur ACPT-Lite-Select-Felder dürfen Optionen enthalten.', 'wp-agent-companion' ), array( 'status' => 409 ) );
			$fields[] = array( 'id' => $field_id, 'name' => $field_name, 'label' => $field_label, 'type' => $type_map[ $provider_type ], 'required' => $required, 'sort' => $field_sort, 'options' => $options );
		}
		$boxes[] = array( 'id' => $box_id, 'name' => $box_name, 'label' => $box_label, 'sort' => $box_sort, 'fields' => $fields );
	}
	$state = array( 'id' => $id, 'binding_id' => $binding_id, 'name' => $name, 'label' => $label, 'target' => $target, 'context' => $context, 'priority' => $priority, 'boxes' => $boxes, 'provider_version' => $provider_version );
	$state['definition_hash'] = wpagent_acpt_hash( array( 'blog_id' => get_current_blog_id(), 'provider_version' => $provider_version, 'definition' => $state ) );
	return $state;
}

/** @return array<string,mixed>|WP_Error */
function wpagent_acpt_meta_group_state( string $name ) {
	$provider = wpagent_acpt_meta_provider();
	if ( is_wp_error( $provider ) ) return $provider;
	$cache = wpagent_acpt_flush_provider_cache( $provider );
	if ( is_wp_error( $cache ) ) return $cache;
	try { $models = $provider['repository']::get( array( 'groupName' => $name ) ); } catch ( Throwable $error ) { return new WP_Error( 'wpagent_acpt_meta_read', __( 'Die ACPT-Lite-Metagruppe konnte nicht gelesen werden.', 'wp-agent-companion' ), array( 'status' => 500 ) ); }
	if ( ! is_array( $models ) || count( $models ) > 1 ) return new WP_Error( 'wpagent_acpt_meta_ambiguous', __( 'ACPT Lite lieferte eine mehrdeutige Metagruppe.', 'wp-agent-companion' ), array( 'status' => 500 ) );
	if ( array() === $models ) return new WP_Error( 'wpagent_acpt_meta_not_found', __( 'ACPT-Lite-Metagruppe nicht gefunden.', 'wp-agent-companion' ), array( 'status' => 404 ) );
	return wpagent_acpt_meta_group_state_from_model( $models[0], $provider['version'] );
}

/** @return array<string,mixed> */
function wpagent_acpt_meta_public_definition( array $state ): array {
	$boxes = array();
	foreach ( $state['boxes'] as $box ) {
		$fields = array();
		foreach ( $box['fields'] as $field ) {
			$fields[] = array( 'name' => $field['name'], 'label' => $field['label'], 'type' => $field['type'], 'required' => $field['required'], 'options' => array_map( static fn( array $option ): array => array( 'label' => $option['label'], 'value' => $option['value'] ), $field['options'] ) );
		}
		$boxes[] = array( 'name' => $box['name'], 'label' => $box['label'], 'fields' => $fields );
	}
	return array( 'name' => $state['name'], 'label' => $state['label'], 'target' => $state['target'], 'context' => $state['context'], 'priority' => $state['priority'], 'boxes' => $boxes );
}

/** @return array<string,mixed> */
function wpagent_acpt_meta_provider_payload( array $definition, ?array $existing = null ): array {
	$payload = array( 'name' => $definition['name'], 'label' => $definition['label'], 'display' => 'standard', 'context' => $definition['context'], 'priority' => $definition['priority'], 'belongs' => array(), 'boxes' => array() );
	$belong_id = is_array( $existing ) ? (string) ( $existing['binding_id'] ?? '' ) : '';
	$payload['belongs'][] = wpagent_acpt_meta_belong_from_target( $definition['target'], $belong_id );
	if ( is_array( $existing ) ) $payload['id'] = $existing['id'];
	foreach ( $definition['boxes'] as $box_index => $box ) {
		$old_box = is_array( $existing ) ? $existing['boxes'][ $box_index ] : null;
		$provider_box = array( 'name' => $box['name'], 'label' => $box['label'], 'fields' => array() );
		if ( is_array( $old_box ) ) $provider_box['id'] = $old_box['id'];
		foreach ( $box['fields'] as $field_index => $field ) {
			$old_field = is_array( $old_box ) ? $old_box['fields'][ $field_index ] : null;
			$provider_field = array( 'name' => $field['name'], 'label' => $field['label'], 'type' => $field['provider_type'] ?? ucfirst( $field['type'] ), 'isRequired' => $field['required'], 'showInArchive' => false, 'quickEdit' => false, 'filterableInAdmin' => false, 'defaultValue' => null, 'description' => null, 'options' => array() );
			if ( is_array( $old_field ) ) $provider_field['id'] = $old_field['id'];
			foreach ( $field['options'] as $option_index => $option ) {
				$provider_option = array( 'label' => $option['label'], 'value' => $option['value'], 'isDefault' => false );
				if ( is_array( $old_field ) && isset( $old_field['options'][ $option_index ]['id'] ) ) $provider_option['id'] = $old_field['options'][ $option_index ]['id'];
				$provider_field['options'][] = $provider_option;
			}
			$provider_box['fields'][] = $provider_field;
		}
		$payload['boxes'][] = $provider_box;
	}
	return $payload;
}

function wpagent_acpt_meta_lock_name( string $scope ): string {
	return 'wp_agent_acpt_meta_lock_' . substr( hash( 'sha256', get_current_blog_id() . "\0" . $scope ), 0, 32 );
}

/** @return string|false */
function wpagent_acpt_meta_acquire_lock( string $scope ) {
	return wpagent_companion_acquire_option_lock( wpagent_acpt_meta_lock_name( $scope ), WPAGENT_ACPT_META_LOCK_TTL );
}

function wpagent_acpt_meta_release_lock( string $scope, string $token ): void {
	wpagent_companion_release_option_lock( wpagent_acpt_meta_lock_name( $scope ), $token );
}

function wpagent_acpt_meta_refresh_lock( string $scope, string $token ): bool {
	return wpagent_companion_refresh_option_lock( wpagent_acpt_meta_lock_name( $scope ), $token, WPAGENT_ACPT_META_LOCK_TTL );
}

/** @return array<string,mixed>|WP_Error */
function wpagent_acpt_meta_object_context( string $object_type, $object_id, array $group ) {
	$id = filter_var( $object_id, FILTER_VALIDATE_INT, array( 'options' => array( 'min_range' => 1 ) ) );
	if ( ! $id || ! in_array( $object_type, array( 'post', 'term', 'user', 'comment' ), true ) ) return new WP_Error( 'wpagent_acpt_meta_object', __( 'object_type und object_id bezeichnen kein unterstütztes Zielobjekt.', 'wp-agent-companion' ), array( 'status' => 400 ) );
	$id = (int) $id;
	$target = $group['target'];
	if ( 'post' === $object_type ) {
		$post = get_post( $id );
		if ( ! $post ) return new WP_Error( 'wpagent_acpt_meta_object', __( 'Beitrag nicht gefunden.', 'wp-agent-companion' ), array( 'status' => 404 ) );
		if ( 'post_type' !== $target['type'] || $post->post_type !== $target['name'] ) return new WP_Error( 'wpagent_acpt_meta_assignment', __( 'Die Metagruppe ist diesem Beitragstyp nicht zugeordnet.', 'wp-agent-companion' ), array( 'status' => 409 ) );
		return array( 'type' => 'post', 'id' => $id, 'provider_key' => 'post_id', 'capability' => array( 'edit_post', $id ) );
	}
	if ( 'term' === $object_type ) {
		$term = get_term( $id );
		if ( ! $term instanceof WP_Term ) return new WP_Error( 'wpagent_acpt_meta_object', __( 'Begriff nicht gefunden.', 'wp-agent-companion' ), array( 'status' => 404 ) );
		if ( 'taxonomy' !== $target['type'] || $term->taxonomy !== $target['name'] ) return new WP_Error( 'wpagent_acpt_meta_assignment', __( 'Die Metagruppe ist dieser Taxonomie nicht zugeordnet.', 'wp-agent-companion' ), array( 'status' => 409 ) );
		return array( 'type' => 'term', 'id' => $id, 'provider_key' => 'term_id', 'capability' => array( 'edit_term', $id ) );
	}
	if ( 'user' === $object_type ) {
		if ( ! get_userdata( $id ) ) return new WP_Error( 'wpagent_acpt_meta_object', __( 'Benutzer nicht gefunden.', 'wp-agent-companion' ), array( 'status' => 404 ) );
		if ( 'user' !== $target['type'] ) return new WP_Error( 'wpagent_acpt_meta_assignment', __( 'Die Metagruppe ist Benutzern nicht zugeordnet.', 'wp-agent-companion' ), array( 'status' => 409 ) );
		return array( 'type' => 'user', 'id' => $id, 'provider_key' => 'user_id', 'capability' => array( 'edit_user', $id ) );
	}
	$comment = get_comment( $id );
	if ( ! $comment ) return new WP_Error( 'wpagent_acpt_meta_object', __( 'Kommentar nicht gefunden.', 'wp-agent-companion' ), array( 'status' => 404 ) );
	if ( 'comment' !== $target['type'] ) return new WP_Error( 'wpagent_acpt_meta_assignment', __( 'Die Metagruppe ist Kommentaren nicht zugeordnet.', 'wp-agent-companion' ), array( 'status' => 409 ) );
	return array( 'type' => 'comment', 'id' => $id, 'provider_key' => 'comment_id', 'capability' => array( 'edit_comment', $id ) );
}

function wpagent_acpt_meta_can_edit_object( array $context ): bool {
	return current_user_can( (string) $context['capability'][0], (int) $context['capability'][1] );
}

/** @return array<string,mixed>|WP_Error */
function wpagent_acpt_meta_values_state( array $group, array $context ) {
	$values = array();
	$types = array();
	foreach ( $group['boxes'] as $box ) {
		foreach ( $box['fields'] as $field ) {
			$key = $box['name'] . '_' . $field['name'];
			$exists = metadata_exists( $context['type'], $context['id'], $key );
			$value = $exists ? get_metadata( $context['type'], $context['id'], $key, true ) : null;
			$type = metadata_exists( $context['type'], $context['id'], $key . '_type' ) ? get_metadata( $context['type'], $context['id'], $key . '_type', true ) : null;
			$expected_type = ucfirst( $field['type'] );
			if ( ( null !== $value && ! is_string( $value ) ) || ( null !== $type && ! is_string( $type ) ) || ( is_string( $value ) && wpagent_acpt_text_length( $value ) > 5000 ) || ( $exists && $expected_type !== $type ) ) {
				return new WP_Error( 'wpagent_acpt_meta_value_state', __( 'Ein gespeicherter ACPT-Lite-Wert liegt außerhalb des sicheren Rohdatenvertrags.', 'wp-agent-companion' ), array( 'status' => 409 ) );
			}
			$values[ $field['name'] ] = $value;
			$types[ $field['name'] ] = $type;
		}
	}
	ksort( $values, SORT_STRING );
	ksort( $types, SORT_STRING );
	$hash = wpagent_acpt_hash( array( 'blog_id' => get_current_blog_id(), 'provider_version' => $group['provider_version'], 'definition_hash' => $group['definition_hash'], 'object_type' => $context['type'], 'object_id' => $context['id'], 'values' => $values, 'types' => $types ) );
	return array( 'group' => $group['name'], 'object_type' => $context['type'], 'object_id' => $context['id'], 'values' => $values, 'values_hash' => $hash, 'provider_version' => $group['provider_version'] );
}

/** @return mixed|WP_Error */
function wpagent_acpt_meta_validate_value( array $field, $value ) {
	if ( ! is_string( $value ) ) return new WP_Error( 'wpagent_acpt_meta_value', __( 'ACPT-Lite-Werte müssen Zeichenketten sein.', 'wp-agent-companion' ), array( 'status' => 400 ) );
	if ( ! empty( $field['required'] ) && '' === $value ) return new WP_Error( 'wpagent_acpt_meta_value', __( 'Ein erforderliches ACPT-Lite-Feld darf nicht geleert werden.', 'wp-agent-companion' ), array( 'status' => 400 ) );
	if ( '' === $value ) return $value;
	$max = 'textarea' === $field['type'] ? 5000 : 500;
	if ( wpagent_acpt_text_length( $value ) > $max || sanitize_text_field( $value ) !== $value ) return new WP_Error( 'wpagent_acpt_meta_value', __( 'Der ACPT-Lite-Wert ist zu lang oder enthält nicht freigegebenen aktiven Inhalt.', 'wp-agent-companion' ), array( 'status' => 400 ) );
	if ( 'email' === $field['type'] && sanitize_email( $value ) !== $value ) return new WP_Error( 'wpagent_acpt_meta_value', __( 'Der ACPT-Lite-E-Mail-Wert ist ungültig.', 'wp-agent-companion' ), array( 'status' => 400 ) );
	if ( 'date' === $field['type'] ) {
		$parts = array_map( 'intval', explode( '-', $value ) );
		if ( ! preg_match( '/^[0-9]{4}-[0-9]{2}-[0-9]{2}$/D', $value ) || 3 !== count( $parts ) || ! checkdate( $parts[1], $parts[2], $parts[0] ) ) return new WP_Error( 'wpagent_acpt_meta_value', __( 'Der ACPT-Lite-Datumswert muss ein echtes Datum im Format JJJJ-MM-TT sein.', 'wp-agent-companion' ), array( 'status' => 400 ) );
	}
	if ( 'select' === $field['type'] && ! in_array( $value, array_column( $field['options'], 'value' ), true ) ) return new WP_Error( 'wpagent_acpt_meta_value', __( 'Der ACPT-Lite-Select-Wert ist nicht als Option registriert.', 'wp-agent-companion' ), array( 'status' => 400 ) );
	return $value;
}
