<?php
/** Shared helpers for persistent, verified ACF field-group management. */

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

define( 'WPAGENT_ACF_GROUP_MAX_FIELDS', 100 );
define( 'WPAGENT_ACF_GROUP_MAX_CHOICES', 200 );
define( 'WPAGENT_ACF_GROUP_MAX_DEFAULT_ITEMS', 200 );
define( 'WPAGENT_ACF_GROUP_MAX_DEFAULT_TEXT_LENGTH', 10000 );
define( 'WPAGENT_ACF_GROUP_MAX_LOCATION_GROUPS', 20 );
define( 'WPAGENT_ACF_GROUP_MAX_LOCATION_RULES', 100 );
define( 'WPAGENT_ACF_GROUP_MAX_LIST_TOTAL', 100000 );
define( 'WPAGENT_ACF_GROUP_MAX_OUTPUT_BYTES', 100000 );

/** @return string[] */
function wpagent_acf_group_supported_field_types(): array {
	return array( 'text', 'textarea', 'number', 'range', 'email', 'url', 'true_false', 'select', 'checkbox', 'radio', 'button_group' );
}

function wpagent_acf_group_text_length( string $value ): int {
	return function_exists( 'mb_strlen' ) ? mb_strlen( $value, 'UTF-8' ) : strlen( $value );
}

function wpagent_acf_group_boolean( $value ): bool {
	return is_bool( $value ) ? $value : rest_sanitize_boolean( $value );
}

/**
 * Supported field defaults are scalar values or a flat choice list. Keeping
 * this schema non-recursive prevents an ACF definition from expanding the MCP
 * schema tree while matching every field type this public path supports.
 *
 * @return array<string,mixed>
 */
function wpagent_acf_group_default_value_schema(): array {
	return array(
		'type' => array( 'string', 'number', 'boolean', 'null', 'array' ),
		'maxLength' => WPAGENT_ACF_GROUP_MAX_DEFAULT_TEXT_LENGTH,
		'maxItems' => WPAGENT_ACF_GROUP_MAX_DEFAULT_ITEMS,
		'items' => array(
			'type' => array( 'string', 'number', 'boolean' ),
			'maxLength' => WPAGENT_ACF_GROUP_MAX_DEFAULT_TEXT_LENGTH,
		),
	);
}

function wpagent_acf_group_default_value_is_bounded( $value ): bool {
	if ( is_string( $value ) ) {
		return wpagent_acf_group_text_length( $value ) <= WPAGENT_ACF_GROUP_MAX_DEFAULT_TEXT_LENGTH;
	}
	if ( null === $value || is_bool( $value ) || is_int( $value ) ) {
		return true;
	}
	if ( is_float( $value ) ) {
		return is_finite( $value );
	}
	if ( ! is_array( $value ) || ! array_is_list( $value ) || count( $value ) > WPAGENT_ACF_GROUP_MAX_DEFAULT_ITEMS ) {
		return false;
	}
	foreach ( $value as $item ) {
		if ( ! is_string( $item ) && ! is_bool( $item ) && ! is_int( $item ) && ! is_float( $item ) ) {
			return false;
		}
		if ( is_float( $item ) && ! is_finite( $item ) ) {
			return false;
		}
		if ( is_string( $item ) && wpagent_acf_group_text_length( $item ) > WPAGENT_ACF_GROUP_MAX_DEFAULT_TEXT_LENGTH ) {
			return false;
		}
	}
	return true;
}

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

/** @return true|WP_Error */
function wpagent_acf_group_require_api() {
	$functions = array(
		'acf_get_field_group', 'acf_get_raw_field_groups', 'acf_get_fields',
		'acf_update_field_group', 'acf_delete_field_group', 'acf_update_field', 'acf_delete_field', 'acf_get_field', 'acf_validate_field', 'acf_validate_field_group',
	);
	foreach ( $functions as $function ) {
		if ( ! function_exists( $function ) ) {
			return new WP_Error( 'wpagent_acf_missing', __( 'Advanced Custom Fields ist nicht aktiv oder seine Feldgruppen-API ist nicht verfügbar.', 'wp-agent-companion' ), array( 'status' => 409 ) );
		}
	}
	return true;
}

/** @return array<string,mixed> */
function wpagent_acf_group_definition_state( array $group ): array {
	$group = acf_validate_field_group( $group );
	unset( $group['ID'], $group['local'], $group['_valid'], $group['_prepare'], $group['fields'] );
	return wpagent_acf_group_canonicalize( $group );
}

function wpagent_acf_group_definition_matches( array $actual, array $expected ): bool {
	return wp_json_encode( wpagent_acf_group_definition_state( $actual ) ) === wp_json_encode( wpagent_acf_group_definition_state( $expected ) );
}

/** @return array<string,mixed> */
function wpagent_acf_group_field_schema( bool $input = false ): array {
	$properties = array(
		'key'           => array( 'type' => 'string', 'minLength' => 7, 'maxLength' => 127, 'pattern' => '^field_[A-Za-z0-9_-]{1,120}$' ),
		'label'         => array( 'type' => 'string', 'minLength' => 1, 'maxLength' => 200 ),
		'name'          => array( 'type' => 'string', 'minLength' => 1, 'maxLength' => 64, 'pattern' => '^[a-z][a-z0-9_]{0,63}$' ),
		'type'          => $input ? array( 'type' => 'string', 'enum' => wpagent_acf_group_supported_field_types() ) : array( 'type' => 'string', 'minLength' => 1, 'maxLength' => 64, 'pattern' => '^[A-Za-z0-9_-]{1,64}$' ),
		'instructions'  => array( 'type' => 'string', 'maxLength' => 2000 ),
		'required'      => array( 'type' => 'boolean' ),
		'default_value' => wpagent_acf_group_default_value_schema(),
		'placeholder'   => array( 'type' => 'string', 'maxLength' => 1000 ),
		'choices'       => array( 'type' => 'object', 'maxProperties' => WPAGENT_ACF_GROUP_MAX_CHOICES, 'additionalProperties' => array( 'type' => 'string', 'maxLength' => 200 ) ),
		'multiple'      => array( 'type' => 'boolean' ),
		'min'           => array( 'type' => array( 'number', 'string' ), 'maxLength' => 100 ),
		'max'           => array( 'type' => array( 'number', 'string' ), 'maxLength' => 100 ),
		'step'          => array( 'type' => array( 'number', 'string' ), 'maxLength' => 100 ),
		'rows'          => array( 'type' => 'integer', 'minimum' => 2, 'maximum' => 30 ),
	);
	$schema = array(
		'type' => 'object',
		'properties' => $properties,
		'maxProperties' => 13,
		'additionalProperties' => false,
		'required' => $input ? array( 'label', 'name', 'type' ) : array( 'key', 'label', 'name', 'type', 'instructions', 'required', 'default_value', 'placeholder' ),
	);
	return $schema;
}

/** @return array<string,mixed> */
function wpagent_acf_group_output_schema(): array {
	return array(
		'type' => 'object',
		'properties' => array(
			'id'             => array( 'type' => 'integer', 'minimum' => 1, 'maximum' => 2147483647 ),
			'key'            => array( 'type' => 'string', 'minLength' => 7, 'maxLength' => 127, 'pattern' => '^group_[A-Za-z0-9_-]{1,120}$' ),
			'title'          => array( 'type' => 'string', 'minLength' => 1, 'maxLength' => 200 ),
			'active'         => array( 'type' => 'boolean' ),
			'post_types'     => array( 'type' => 'array', 'maxItems' => 20, 'items' => array( 'type' => 'string', 'minLength' => 1, 'maxLength' => 20, 'pattern' => '^[a-z0-9_-]{1,20}$' ) ),
			'fields'         => array( 'type' => 'array', 'maxItems' => WPAGENT_ACF_GROUP_MAX_FIELDS, 'items' => wpagent_acf_group_field_schema() ),
			'field_count'    => array( 'type' => 'integer', 'minimum' => 0, 'maximum' => WPAGENT_ACF_GROUP_MAX_FIELDS ),
			'fields_truncated' => array( 'type' => 'boolean' ),
			'portable'       => array( 'type' => 'boolean' ),
			'location_rules' => array( 'type' => 'integer', 'minimum' => 0, 'maximum' => WPAGENT_ACF_GROUP_MAX_LOCATION_RULES ),
			'hash'           => array( 'type' => 'string', 'pattern' => '^[a-f0-9]{64}$' ),
		),
		'required' => array( 'id', 'key', 'title', 'active', 'post_types', 'fields', 'field_count', 'fields_truncated', 'portable', 'location_rules', 'hash' ),
		'maxProperties' => 11,
		'additionalProperties' => false,
	);
}

/** @return array<string,mixed>|WP_Error */
function wpagent_acf_group_get_persistent( string $group_key ) {
	if ( ! preg_match( '/^group_[A-Za-z0-9_-]{1,120}$/', $group_key ) ) {
		return new WP_Error( 'wpagent_acf_group_key', __( 'group_key muss ein gültiger ACF-Feldgruppen-Key sein.', 'wp-agent-companion' ), array( 'status' => 400 ) );
	}
	$group = acf_get_field_group( $group_key );
	$id = is_array( $group ) ? absint( $group['ID'] ?? 0 ) : 0;
	if ( ! is_array( $group ) || $id < 1 || ! empty( $group['local'] ) || 'acf-field-group' !== get_post_type( $id ) ) {
		return new WP_Error( 'wpagent_acf_group_not_found', __( 'Persistente ACF-Feldgruppe nicht gefunden. In PHP oder Local JSON registrierte Gruppen werden über diesen Pfad nicht verändert.', 'wp-agent-companion' ), array( 'status' => 404 ) );
	}
	return $group;
}

/** @return array{post_types:string[],portable:bool,count:int} */
function wpagent_acf_group_location_state( array $location ): array {
	$post_types = array();
	$count = 0;
	$portable = array() !== $location;
	foreach ( $location as $rules ) {
		if ( ! is_array( $rules ) || 1 !== count( $rules ) || ! is_array( $rules[0] ?? null ) ) {
			$portable = false;
			$count += is_array( $rules ) ? count( $rules ) : 1;
			continue;
		}
		++$count;
		$rule = $rules[0];
		$value = (string) ( $rule['value'] ?? '' );
		if ( 'post_type' !== (string) ( $rule['param'] ?? '' ) || '==' !== (string) ( $rule['operator'] ?? '' ) || '' === $value ) {
			$portable = false;
			continue;
		}
		$post_types[] = $value;
	}
	$post_types = array_values( array_unique( $post_types ) );
	sort( $post_types, SORT_STRING );
	return array( 'post_types' => $post_types, 'portable' => $portable, 'count' => $count );
}

/** @return array<string,mixed> */
function wpagent_acf_group_field_state( array $field ): array {
	$type = (string) ( $field['type'] ?? '' );
	$default_value = $field['default_value'] ?? '';
	$default_value_available = wpagent_acf_group_default_value_is_bounded( $default_value );
	$state = array(
		'key'           => (string) ( $field['key'] ?? '' ),
		'label'         => (string) ( $field['label'] ?? '' ),
		'name'          => (string) ( $field['name'] ?? '' ),
		'type'          => $type,
		'instructions'  => (string) ( $field['instructions'] ?? '' ),
		'required'      => (bool) ( $field['required'] ?? false ),
		'default_value' => $default_value_available ? $default_value : null,
		'placeholder'   => (string) ( $field['placeholder'] ?? '' ),
	);
	if ( in_array( $type, array( 'select', 'checkbox', 'radio', 'button_group' ), true ) ) {
		$choices = is_array( $field['choices'] ?? null ) ? $field['choices'] : array();
		$state['choices'] = array_slice( $choices, 0, WPAGENT_ACF_GROUP_MAX_CHOICES, true );
	}
	if ( 'select' === $type ) {
		$state['multiple'] = (bool) ( $field['multiple'] ?? false );
	}
	if ( in_array( $type, array( 'number', 'range' ), true ) ) {
		$state['min'] = $field['min'] ?? '';
		$state['max'] = $field['max'] ?? '';
		$state['step'] = $field['step'] ?? '';
	}
	if ( 'textarea' === $type ) {
		$state['rows'] = max( 2, min( 30, absint( $field['rows'] ?? 8 ) ) );
	}
	if ( $default_value_available && 'true_false' === $type ) {
		$state['default_value'] = (bool) $state['default_value'];
	}
	if ( $default_value_available && 'checkbox' === $type && ! is_array( $state['default_value'] ) ) {
		$state['default_value'] = array_filter( array( (string) $state['default_value'] ), 'strlen' );
	}
	return $state;
}

/** @return array<string,mixed> */
function wpagent_acf_group_field_definition_state( array $field ): array {
	$field = acf_parse_types( wp_unslash( acf_validate_field( $field ) ) );
	unset( $field['ID'], $field['parent'], $field['prefix'], $field['_valid'], $field['_prepare'], $field['value'] );
	return wpagent_acf_group_canonicalize( $field );
}

function wpagent_acf_group_field_is_portable( array $field ): bool {
	$key = is_string( $field['key'] ?? null ) ? $field['key'] : '';
	if ( ! preg_match( '/^field_[A-Za-z0-9_-]{1,120}$/', $key ) ) {
		return false;
	}
	$portable = wpagent_acf_group_validate_field( wpagent_acf_group_field_state( $field ), array( $key => true ) );
	if ( is_wp_error( $portable ) ) {
		return false;
	}
	$expected = array_merge(
		$portable,
		array(
			'ID' => absint( $field['ID'] ?? 0 ),
			'key' => $key,
			'parent' => absint( $field['parent'] ?? 0 ),
			'menu_order' => absint( $field['menu_order'] ?? 0 ),
			'conditional_logic' => 0,
			'wrapper' => array( 'width' => '', 'class' => '', 'id' => '' ),
		)
	);
	return wp_json_encode( wpagent_acf_group_field_definition_state( $field ) ) === wp_json_encode( wpagent_acf_group_field_definition_state( $expected ) );
}

/** @return array<string,mixed> */
function wpagent_acf_group_state( array $group ): array {
	$fields = acf_get_fields( $group );
	$fields = is_array( $fields ) ? $fields : array();
	$field_states = array_map( 'wpagent_acf_group_field_state', array_slice( $fields, 0, WPAGENT_ACF_GROUP_MAX_FIELDS ) );
	$location = is_array( $group['location'] ?? null ) ? $group['location'] : array();
	$location_state = wpagent_acf_group_location_state( $location );
	$portable = $location_state['portable'] && count( $fields ) <= WPAGENT_ACF_GROUP_MAX_FIELDS;
	foreach ( $fields as $field ) {
		if ( ! is_array( $field ) || ! in_array( (string) ( $field['type'] ?? '' ), wpagent_acf_group_supported_field_types(), true ) || ! empty( $field['conditional_logic'] ) || count( is_array( $field['choices'] ?? null ) ? $field['choices'] : array() ) > WPAGENT_ACF_GROUP_MAX_CHOICES || ! wpagent_acf_group_field_is_portable( $field ) ) {
			$portable = false;
		}
	}
	$state = array(
		'id'             => absint( $group['ID'] ?? 0 ),
		'key'            => (string) ( $group['key'] ?? '' ),
		'title'          => (string) ( $group['title'] ?? '' ),
		'active'         => (bool) ( $group['active'] ?? false ),
		'post_types'     => $location_state['post_types'],
		'fields'         => $field_states,
		'field_count'    => count( $fields ),
		'fields_truncated' => count( $fields ) > WPAGENT_ACF_GROUP_MAX_FIELDS,
		'portable'       => $portable,
		'location_rules' => $location_state['count'],
	);
	$hash_group = wpagent_acf_group_definition_state( $group );
	$hash_fields = array();
	foreach ( $fields as $field ) {
		if ( ! is_array( $field ) ) {
			continue;
		}
		unset( $field['ID'], $field['parent'], $field['prefix'], $field['_valid'], $field['_prepare'], $field['value'] );
		$hash_fields[] = $field;
	}
	$hash_material = wpagent_acf_group_canonicalize( array( 'group' => $hash_group, 'fields' => $hash_fields ) );
	$encoded_hash_material = wp_json_encode( $hash_material, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE );
	$state['hash'] = hash( 'sha256', false === $encoded_hash_material ? serialize( $hash_material ) : $encoded_hash_material );
	return $state;
}

/** @return WP_Error */
function wpagent_acf_group_read_output_error( string $code, string $field ) {
	return new WP_Error(
		$code,
		/* translators: %s: ACF provider output field. */
		sprintf( __( 'Die ACF-Provider-Ausgabe für %s kann nicht innerhalb des veröffentlichten Sicherheitsvertrags dargestellt werden. Die Antwort wurde nicht gekürzt.', 'wp-agent-companion' ), $field ),
		array( 'status' => 500, 'recovery_required' => false )
	);
}

/** @return int|WP_Error */
function wpagent_acf_group_read_integer( $value, int $minimum, int $maximum, string $field ) {
	if ( is_int( $value ) ) {
		$integer = $value;
	} elseif ( is_string( $value ) && 1 === preg_match( '/^(?:0|[1-9][0-9]*)$/D', $value ) ) {
		$integer = (int) $value;
	} else {
		return wpagent_acf_group_read_output_error( 'wpagent_acf_group_output_invalid', $field );
	}
	return $integer >= $minimum && $integer <= $maximum ? $integer : wpagent_acf_group_read_output_error( 'wpagent_acf_group_output_overflow', $field );
}

/** @return bool|WP_Error */
function wpagent_acf_group_read_boolean( $value, string $field ) {
	if ( is_bool( $value ) ) {
		return $value;
	}
	if ( 0 === $value || '0' === $value ) {
		return false;
	}
	if ( 1 === $value || '1' === $value ) {
		return true;
	}
	return wpagent_acf_group_read_output_error( 'wpagent_acf_group_output_invalid', $field );
}

/** @return string|WP_Error */
function wpagent_acf_group_read_text( $value, int $maximum_length, string $field, bool $allow_empty = true ) {
	if ( ! is_string( $value ) || wpagent_acf_group_text_length( $value ) > $maximum_length ) {
		return wpagent_acf_group_read_output_error( 'wpagent_acf_group_output_overflow', $field );
	}
	if ( ! $allow_empty && '' === $value ) {
		return wpagent_acf_group_read_output_error( 'wpagent_acf_group_output_invalid', $field );
	}
	return $value;
}

/** @return string|int|float|WP_Error */
function wpagent_acf_group_read_number( $value, string $field ) {
	if ( is_string( $value ) ) {
		if ( wpagent_acf_group_text_length( $value ) > 100 ) {
			return wpagent_acf_group_read_output_error( 'wpagent_acf_group_output_overflow', $field );
		}
		return '' === $value || ( is_numeric( $value ) && is_finite( (float) $value ) ) ? $value : wpagent_acf_group_read_output_error( 'wpagent_acf_group_output_invalid', $field );
	}
	if ( is_int( $value ) || ( is_float( $value ) && is_finite( $value ) ) ) {
		return $value;
	}
	return wpagent_acf_group_read_output_error( 'wpagent_acf_group_output_invalid', $field );
}

/** @return array<string,string>|WP_Error */
function wpagent_acf_group_read_choices( $value, string $field ) {
	if ( ! is_array( $value ) || array_is_list( $value ) || count( $value ) > WPAGENT_ACF_GROUP_MAX_CHOICES ) {
		return wpagent_acf_group_read_output_error( 'wpagent_acf_group_output_overflow', $field );
	}
	$choices = array();
	foreach ( $value as $choice_value => $label ) {
		if ( ! is_string( $choice_value ) || strlen( $choice_value ) > 100 || '' === $choice_value || 1 === preg_match( '/[\x00-\x1F\x7F]/', $choice_value ) ) {
			return wpagent_acf_group_read_output_error( 'wpagent_acf_group_output_invalid', $field );
		}
		$label = wpagent_acf_group_read_text( $label, 200, $field . '.' . $choice_value, false );
		if ( is_wp_error( $label ) ) {
			return $label;
		}
		$choices[ $choice_value ] = $label;
	}
	return $choices;
}

/** @return array{post_types:string[],portable:bool,count:int}|WP_Error */
function wpagent_acf_group_read_location_state( $location ) {
	if ( ! is_array( $location ) || ! array_is_list( $location ) || count( $location ) > WPAGENT_ACF_GROUP_MAX_LOCATION_GROUPS ) {
		return wpagent_acf_group_read_output_error( 'wpagent_acf_group_output_overflow', 'location' );
	}
	$post_types = array();
	$count = 0;
	$portable = array() !== $location;
	foreach ( $location as $rules ) {
		if ( ! is_array( $rules ) || ! array_is_list( $rules ) || count( $rules ) < 1 ) {
			return wpagent_acf_group_read_output_error( 'wpagent_acf_group_output_invalid', 'location' );
		}
		$count += count( $rules );
		if ( $count > WPAGENT_ACF_GROUP_MAX_LOCATION_RULES ) {
			return wpagent_acf_group_read_output_error( 'wpagent_acf_group_output_overflow', 'location' );
		}
		if ( 1 !== count( $rules ) || ! is_array( $rules[0] ?? null ) ) {
			$portable = false;
			continue;
		}
		$rule = $rules[0];
		$param = wpagent_acf_group_read_text( $rule['param'] ?? null, 64, 'location.param', false );
		$operator = wpagent_acf_group_read_text( $rule['operator'] ?? null, 8, 'location.operator', false );
		$value = wpagent_acf_group_read_text( $rule['value'] ?? null, 20, 'location.value', false );
		if ( is_wp_error( $param ) || is_wp_error( $operator ) || is_wp_error( $value ) ) {
			return is_wp_error( $param ) ? $param : ( is_wp_error( $operator ) ? $operator : $value );
		}
		if ( 'post_type' !== $param || '==' !== $operator || ! preg_match( '/^[a-z0-9_-]{1,20}$/D', $value ) ) {
			$portable = false;
			continue;
		}
		$post_types[] = $value;
	}
	$post_types = array_values( array_unique( $post_types ) );
	if ( count( $post_types ) > 20 ) {
		return wpagent_acf_group_read_output_error( 'wpagent_acf_group_output_overflow', 'post_types' );
	}
	sort( $post_types, SORT_STRING );
	return array( 'post_types' => $post_types, 'portable' => $portable, 'count' => $count );
}

/** @return array<string,mixed>|WP_Error */
function wpagent_acf_group_read_field_state( $field ) {
	if ( ! is_array( $field ) || array_is_list( $field ) ) {
		return wpagent_acf_group_read_output_error( 'wpagent_acf_group_output_invalid', 'fields' );
	}
	$key = wpagent_acf_group_read_text( $field['key'] ?? null, 127, 'fields.key', false );
	$label = wpagent_acf_group_read_text( $field['label'] ?? null, 200, 'fields.label', false );
	$name = wpagent_acf_group_read_text( $field['name'] ?? null, 64, 'fields.name', false );
	$type = wpagent_acf_group_read_text( $field['type'] ?? null, 64, 'fields.type', false );
	$instructions = wpagent_acf_group_read_text( $field['instructions'] ?? '', 2000, 'fields.instructions' );
	$required = wpagent_acf_group_read_boolean( $field['required'] ?? false, 'fields.required' );
	$default_value = $field['default_value'] ?? '';
	$placeholder = wpagent_acf_group_read_text( $field['placeholder'] ?? '', 1000, 'fields.placeholder' );
	foreach ( array( $key, $label, $name, $type, $instructions, $required, $placeholder ) as $value ) {
		if ( is_wp_error( $value ) ) {
			return $value;
		}
	}
	if ( ! preg_match( '/^field_[A-Za-z0-9_-]{1,120}$/D', $key ) || ! preg_match( '/^[a-z][a-z0-9_]{0,63}$/D', $name ) || ! preg_match( '/^[A-Za-z0-9_-]{1,64}$/D', $type ) || ! wpagent_acf_group_default_value_is_bounded( $default_value ) ) {
		return wpagent_acf_group_read_output_error( 'wpagent_acf_group_output_invalid', 'fields' );
	}
	$state = array( 'key' => $key, 'label' => $label, 'name' => $name, 'type' => $type, 'instructions' => $instructions, 'required' => $required, 'default_value' => $default_value, 'placeholder' => $placeholder );
	if ( in_array( $type, array( 'select', 'checkbox', 'radio', 'button_group' ), true ) ) {
		$choices = wpagent_acf_group_read_choices( $field['choices'] ?? null, 'fields.choices' );
		if ( is_wp_error( $choices ) ) {
			return $choices;
		}
		$state['choices'] = $choices;
	}
	if ( 'select' === $type ) {
		$multiple = wpagent_acf_group_read_boolean( $field['multiple'] ?? false, 'fields.multiple' );
		if ( is_wp_error( $multiple ) ) {
			return $multiple;
		}
		$state['multiple'] = $multiple;
	}
	if ( in_array( $type, array( 'number', 'range' ), true ) ) {
		foreach ( array( 'min', 'max', 'step' ) as $bound ) {
			$number = wpagent_acf_group_read_number( $field[ $bound ] ?? '', 'fields.' . $bound );
			if ( is_wp_error( $number ) ) {
				return $number;
			}
			$state[ $bound ] = $number;
		}
	}
	if ( 'textarea' === $type ) {
		$rows = wpagent_acf_group_read_integer( $field['rows'] ?? 8, 2, 30, 'fields.rows' );
		if ( is_wp_error( $rows ) ) {
			return $rows;
		}
		$state['rows'] = $rows;
	}
	return $state;
}

/** @return true|WP_Error */
function wpagent_acf_group_read_output_bytes( array $state, string $field ) {
	$encoded = wp_json_encode( $state, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE );
	if ( ! is_string( $encoded ) ) {
		return wpagent_acf_group_read_output_error( 'wpagent_acf_group_output_invalid', $field );
	}
	return strlen( $encoded ) <= WPAGENT_ACF_GROUP_MAX_OUTPUT_BYTES ? true : wpagent_acf_group_read_output_error( 'wpagent_acf_group_output_overflow', $field );
}

/** @return array<string,mixed>|WP_Error */
function wpagent_acf_group_read_state( array $group ) {
	$id = wpagent_acf_group_read_integer( $group['ID'] ?? null, 1, 2147483647, 'id' );
	$key = wpagent_acf_group_read_text( $group['key'] ?? null, 127, 'key', false );
	$title = wpagent_acf_group_read_text( $group['title'] ?? null, 200, 'title', false );
	$active = wpagent_acf_group_read_boolean( $group['active'] ?? false, 'active' );
	foreach ( array( $id, $key, $title, $active ) as $value ) {
		if ( is_wp_error( $value ) ) {
			return $value;
		}
	}
	if ( ! preg_match( '/^group_[A-Za-z0-9_-]{1,120}$/D', $key ) ) {
		return wpagent_acf_group_read_output_error( 'wpagent_acf_group_output_invalid', 'key' );
	}
	$location = wpagent_acf_group_read_location_state( $group['location'] ?? array() );
	if ( is_wp_error( $location ) ) {
		return $location;
	}
	$fields = acf_get_fields( $group );
	if ( false === $fields ) {
		$fields = array();
	}
	if ( ! is_array( $fields ) || ! array_is_list( $fields ) || count( $fields ) > WPAGENT_ACF_GROUP_MAX_FIELDS ) {
		return wpagent_acf_group_read_output_error( 'wpagent_acf_group_output_overflow', 'fields' );
	}
	$field_states = array();
	$portable = $location['portable'];
	foreach ( $fields as $field ) {
		$state = wpagent_acf_group_read_field_state( $field );
		if ( is_wp_error( $state ) ) {
			return $state;
		}
		$field_states[] = $state;
		if ( ! in_array( $state['type'], wpagent_acf_group_supported_field_types(), true ) || ! empty( $field['conditional_logic'] ) || ! wpagent_acf_group_field_is_portable( $field ) ) {
			$portable = false;
		}
	}
	$hash_group = wpagent_acf_group_definition_state( $group );
	$hash_fields = array();
	foreach ( $fields as $field ) {
		unset( $field['ID'], $field['parent'], $field['prefix'], $field['_valid'], $field['_prepare'], $field['value'] );
		$hash_fields[] = $field;
	}
	$hash_material = wpagent_acf_group_canonicalize( array( 'group' => $hash_group, 'fields' => $hash_fields ) );
	$encoded_hash_material = wp_json_encode( $hash_material, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE );
	$state = array(
		'id' => $id,
		'key' => $key,
		'title' => $title,
		'active' => $active,
		'post_types' => $location['post_types'],
		'fields' => $field_states,
		'field_count' => count( $fields ),
		'fields_truncated' => false,
		'portable' => $portable,
		'location_rules' => $location['count'],
		'hash' => hash( 'sha256', false === $encoded_hash_material ? serialize( $hash_material ) : $encoded_hash_material ),
	);
	$valid = wpagent_acf_group_read_output_bytes( $state, 'field_group' );
	return is_wp_error( $valid ) ? $valid : $state;
}

/** @return string[]|WP_Error */
function wpagent_acf_group_validate_post_types( $value ) {
	if ( ! is_array( $value ) || ! array_is_list( $value ) || array() === $value || count( $value ) > 20 ) {
		return new WP_Error( 'wpagent_acf_group_post_types', __( 'post_types muss eine Liste mit 1-20 registrierten Inhaltstypen sein.', 'wp-agent-companion' ), array( 'status' => 400 ) );
	}
	$result = array();
	foreach ( $value as $post_type ) {
		$post_type = is_string( $post_type ) ? sanitize_key( $post_type ) : '';
		$object = get_post_type_object( $post_type );
		if ( '' === $post_type || ! $object || empty( $object->show_ui ) ) {
			return new WP_Error( 'wpagent_acf_group_post_type', __( 'Ein post_type ist nicht registriert oder besitzt keine Verwaltungsoberfläche.', 'wp-agent-companion' ), array( 'status' => 400 ) );
		}
		$result[] = $post_type;
	}
	$result = array_values( array_unique( $result ) );
	sort( $result, SORT_STRING );
	return $result;
}

/** @return array<int,array<string,mixed>> */
function wpagent_acf_group_location_for_post_types( array $post_types ): array {
	return array_map(
		static fn( string $post_type ): array => array( array( 'param' => 'post_type', 'operator' => '==', 'value' => $post_type ) ),
		$post_types
	);
}

/** @return array<string,string>|WP_Error */
function wpagent_acf_group_validate_choices( $value ) {
	if ( ! is_array( $value ) || array_is_list( $value ) || count( $value ) > WPAGENT_ACF_GROUP_MAX_CHOICES ) {
		return new WP_Error( 'wpagent_acf_group_choices', __( 'choices muss ein Objekt mit höchstens 200 Einträgen sein.', 'wp-agent-companion' ), array( 'status' => 400 ) );
	}
	$result = array();
	foreach ( $value as $choice_value => $label ) {
		if ( is_int( $choice_value ) ) {
			return new WP_Error( 'wpagent_acf_group_choice', __( 'Numerische Auswahlschlüssel sind im JSON-sicheren Feldgruppenpfad nicht unterstützt.', 'wp-agent-companion' ), array( 'status' => 400 ) );
		}
		$choice_value = (string) $choice_value;
		$label = is_string( $label ) ? trim( $label ) : '';
		if ( '' === $choice_value || strlen( $choice_value ) > 100 || preg_match( '/[\x00-\x1F\x7F]/', $choice_value ) || '' === $label || wpagent_acf_group_text_length( $label ) > 200 || sanitize_text_field( $label ) !== $label ) {
			return new WP_Error( 'wpagent_acf_group_choice', __( 'Jeder Auswahlwert benötigt einen kurzen Schlüssel und eine reine Textbeschriftung.', 'wp-agent-companion' ), array( 'status' => 400 ) );
		}
		$result[ $choice_value ] = $label;
	}
	return $result;
}

/** @return string|WP_Error */
function wpagent_acf_group_validate_text_default( $value, string $type ) {
	if ( ! is_string( $value ) ) {
		return new WP_Error( 'wpagent_acf_group_default', __( 'Der Standardwert dieses Textfelds muss eine Zeichenfolge sein.', 'wp-agent-companion' ), array( 'status' => 400 ) );
	}
	$max = 'textarea' === $type ? 10000 : 4000;
	$sanitized = 'textarea' === $type ? sanitize_textarea_field( $value ) : sanitize_text_field( $value );
	if ( wpagent_acf_group_text_length( $value ) > $max || $sanitized !== $value ) {
		return new WP_Error( 'wpagent_acf_group_default', __( 'Der Textstandardwert ist zu lang oder enthält nicht erlaubtes Markup.', 'wp-agent-companion' ), array( 'status' => 400 ) );
	}
	if ( 'email' === $type && '' !== $value && ( ! is_email( $value ) || sanitize_email( $value ) !== $value ) ) {
		return new WP_Error( 'wpagent_acf_group_default', __( 'Der E-Mail-Standardwert ist ungültig.', 'wp-agent-companion' ), array( 'status' => 400 ) );
	}
	if ( 'url' === $type && '' !== $value ) {
		$parts = wp_parse_url( $value );
		if ( ! is_array( $parts ) || ! in_array( strtolower( (string) ( $parts['scheme'] ?? '' ) ), array( 'http', 'https' ), true ) || empty( $parts['host'] ) ) {
			return new WP_Error( 'wpagent_acf_group_default', __( 'Der URL-Standardwert muss eine vollständige HTTP- oder HTTPS-URL sein.', 'wp-agent-companion' ), array( 'status' => 400 ) );
		}
	}
	return $value;
}

/** @return array<string,mixed>|WP_Error */
function wpagent_acf_group_validate_field( $value, array $allowed_existing_keys = array() ) {
	if ( ! is_array( $value ) || array_is_list( $value ) ) {
		return new WP_Error( 'wpagent_acf_group_field', __( 'Jedes ACF-Feld muss ein Objekt sein.', 'wp-agent-companion' ), array( 'status' => 400 ) );
	}
	$label = is_string( $value['label'] ?? null ) ? trim( $value['label'] ) : '';
	$name = is_string( $value['name'] ?? null ) ? $value['name'] : '';
	$type = is_string( $value['type'] ?? null ) ? $value['type'] : '';
	if ( '' === $label || wpagent_acf_group_text_length( $label ) > 200 || sanitize_text_field( $label ) !== $label ) {
		return new WP_Error( 'wpagent_acf_group_label', __( 'Jedes ACF-Feld benötigt eine reine Textbeschriftung mit höchstens 200 Zeichen.', 'wp-agent-companion' ), array( 'status' => 400 ) );
	}
	if ( ! preg_match( '/^[a-z][a-z0-9_]{0,63}$/', $name ) ) {
		return new WP_Error( 'wpagent_acf_group_name', __( 'ACF-Feldnamen müssen mit einem Kleinbuchstaben beginnen und dürfen nur Kleinbuchstaben, Ziffern und Unterstriche enthalten.', 'wp-agent-companion' ), array( 'status' => 400 ) );
	}
	if ( ! in_array( $type, wpagent_acf_group_supported_field_types(), true ) ) {
		return new WP_Error( 'wpagent_acf_group_type', __( 'Dieser ACF-Feldtyp ist über den verifizierten Feldgruppenpfad nicht unterstützt.', 'wp-agent-companion' ), array( 'status' => 400 ) );
	}
	if ( ! wpagent_acf_group_default_value_is_bounded( $value['default_value'] ?? '' ) ) {
		return new WP_Error( 'wpagent_acf_group_default', __( 'Der Standardwert ist zu groß, zu komplex oder enthält eine nicht unterstützte verschachtelte Struktur.', 'wp-agent-companion' ), array( 'status' => 400 ) );
	}
	$key = is_string( $value['key'] ?? null ) ? $value['key'] : '';
	if ( '' !== $key && ( ! preg_match( '/^field_[A-Za-z0-9_-]{1,120}$/', $key ) || ! isset( $allowed_existing_keys[ $key ] ) ) ) {
		return new WP_Error( 'wpagent_acf_group_field_key', __( 'Ein vorhandener field_-Key gehört nicht zu dieser Feldgruppe.', 'wp-agent-companion' ), array( 'status' => 400 ) );
	}
	$instructions = is_string( $value['instructions'] ?? null ) ? trim( $value['instructions'] ) : '';
	$placeholder = is_string( $value['placeholder'] ?? null ) ? $value['placeholder'] : '';
	if ( wpagent_acf_group_text_length( $instructions ) > 2000 || sanitize_textarea_field( $instructions ) !== $instructions || wpagent_acf_group_text_length( $placeholder ) > 1000 || sanitize_text_field( $placeholder ) !== $placeholder ) {
		return new WP_Error( 'wpagent_acf_group_text', __( 'Anweisungen oder Platzhalter sind zu lang oder enthalten nicht erlaubtes Markup.', 'wp-agent-companion' ), array( 'status' => 400 ) );
	}
	$field = array(
		'key' => $key,
		'label' => $label,
		'name' => $name,
		'type' => $type,
		'instructions' => $instructions,
		'required' => wpagent_acf_group_boolean( $value['required'] ?? false ),
		'default_value' => $value['default_value'] ?? '',
		'placeholder' => $placeholder,
	);
	if ( in_array( $type, array( 'text', 'textarea', 'email', 'url' ), true ) ) {
		$default = wpagent_acf_group_validate_text_default( $field['default_value'], $type );
		if ( is_wp_error( $default ) ) {
			return $default;
		}
		$field['default_value'] = $default;
	}
	if ( in_array( $type, array( 'select', 'checkbox', 'radio', 'button_group' ), true ) ) {
		$choices = wpagent_acf_group_validate_choices( $value['choices'] ?? array() );
		if ( is_wp_error( $choices ) || array() === $choices ) {
			return is_wp_error( $choices ) ? $choices : new WP_Error( 'wpagent_acf_group_choices', __( 'Auswahlfelder benötigen mindestens einen choices-Eintrag.', 'wp-agent-companion' ), array( 'status' => 400 ) );
		}
		$field['choices'] = $choices;
		$multiple = 'checkbox' === $type || ( 'select' === $type && wpagent_acf_group_boolean( $value['multiple'] ?? false ) );
		$raw_default = $field['default_value'];
		if ( $multiple ) {
			if ( '' === $raw_default || null === $raw_default ) {
				$defaults = array();
			} elseif ( ! is_array( $raw_default ) || ! array_is_list( $raw_default ) ) {
				return new WP_Error( 'wpagent_acf_group_default', __( 'Mehrfachauswahl-Standardwerte müssen als Liste übergeben werden.', 'wp-agent-companion' ), array( 'status' => 400 ) );
			} else {
				$defaults = $raw_default;
			}
		} elseif ( is_array( $raw_default ) || is_object( $raw_default ) ) {
			return new WP_Error( 'wpagent_acf_group_default', __( 'Der Standardwert dieses Auswahlfelds muss ein einzelner Wert sein.', 'wp-agent-companion' ), array( 'status' => 400 ) );
		} else {
			$defaults = array_filter( array( (string) $raw_default ), 'strlen' );
		}
		foreach ( $defaults as $default ) {
			if ( ! is_scalar( $default ) || ! array_key_exists( (string) $default, $choices ) ) {
				return new WP_Error( 'wpagent_acf_group_default', __( 'Ein Standardwert ist nicht in choices enthalten.', 'wp-agent-companion' ), array( 'status' => 400 ) );
			}
		}
		$field['default_value'] = $multiple ? array_map( 'strval', $defaults ) : ( isset( $defaults[0] ) ? (string) $defaults[0] : '' );
		if ( 'select' === $type ) {
			$field['multiple'] = $multiple;
		}
	}
	if ( 'true_false' === $type ) {
		$field['default_value'] = wpagent_acf_group_boolean( $field['default_value'] );
	}
	if ( in_array( $type, array( 'number', 'range' ), true ) ) {
		foreach ( array( 'min', 'max', 'step' ) as $bound ) {
			$number = $value[ $bound ] ?? '';
			if ( '' !== $number && ( ! is_numeric( $number ) || ! is_finite( (float) $number ) ) ) {
				return new WP_Error( 'wpagent_acf_group_number', __( 'min, max und step müssen leer oder endliche Zahlen sein.', 'wp-agent-companion' ), array( 'status' => 400 ) );
			}
			$field[ $bound ] = $number;
		}
		if ( '' !== $field['step'] && (float) $field['step'] <= 0 ) {
			return new WP_Error( 'wpagent_acf_group_number', __( 'step muss größer als 0 sein.', 'wp-agent-companion' ), array( 'status' => 400 ) );
		}
		if ( '' !== $field['min'] && '' !== $field['max'] && (float) $field['min'] > (float) $field['max'] ) {
			return new WP_Error( 'wpagent_acf_group_number', __( 'min darf nicht größer als max sein.', 'wp-agent-companion' ), array( 'status' => 400 ) );
		}
		$default = $field['default_value'];
		if ( '' !== $default && ( ! is_scalar( $default ) || ! is_numeric( $default ) || ! is_finite( (float) $default ) ) ) {
			return new WP_Error( 'wpagent_acf_group_default', __( 'Der Zahlenstandardwert muss leer oder eine endliche Zahl sein.', 'wp-agent-companion' ), array( 'status' => 400 ) );
		}
		if ( '' !== $default && ( ( '' !== $field['min'] && (float) $default < (float) $field['min'] ) || ( '' !== $field['max'] && (float) $default > (float) $field['max'] ) ) ) {
			return new WP_Error( 'wpagent_acf_group_default', __( 'Der Zahlenstandardwert liegt außerhalb von min und max.', 'wp-agent-companion' ), array( 'status' => 400 ) );
		}
	}
	if ( 'textarea' === $type ) {
		$rows = $value['rows'] ?? 8;
		if ( ! is_int( $rows ) || $rows < 2 || $rows > 30 ) {
			return new WP_Error( 'wpagent_acf_group_rows', __( 'rows muss eine Ganzzahl zwischen 2 und 30 sein.', 'wp-agent-companion' ), array( 'status' => 400 ) );
		}
		$field['rows'] = $rows;
	}
	return $field;
}

/** @return array<int,array<string,mixed>>|WP_Error */
function wpagent_acf_group_validate_fields( $values, array $existing_fields = array() ) {
	if ( ! is_array( $values ) || ! array_is_list( $values ) || count( $values ) > WPAGENT_ACF_GROUP_MAX_FIELDS ) {
		return new WP_Error( 'wpagent_acf_group_fields', __( 'fields muss eine Liste mit höchstens 100 Feldern sein.', 'wp-agent-companion' ), array( 'status' => 400 ) );
	}
	$allowed_keys = array();
	foreach ( $existing_fields as $field ) {
		if ( is_array( $field ) && is_string( $field['key'] ?? null ) ) {
			$allowed_keys[ $field['key'] ] = true;
		}
	}
	$result = array();
	$names = array();
	$keys = array();
	foreach ( $values as $value ) {
		$field = wpagent_acf_group_validate_field( $value, $allowed_keys );
		if ( is_wp_error( $field ) ) {
			return $field;
		}
		if ( isset( $names[ $field['name'] ] ) || ( '' !== $field['key'] && isset( $keys[ $field['key'] ] ) ) ) {
			return new WP_Error( 'wpagent_acf_group_duplicate_field', __( 'Feldnamen und vorhandene Feld-Keys müssen innerhalb einer Gruppe eindeutig sein.', 'wp-agent-companion' ), array( 'status' => 400 ) );
		}
		$names[ $field['name'] ] = true;
		if ( '' !== $field['key'] ) {
			$keys[ $field['key'] ] = true;
		}
		$result[] = $field;
	}
	return $result;
}

function wpagent_acf_group_new_key( string $prefix ): string {
	for ( $attempt = 0; $attempt < 10; ++$attempt ) {
		$key = $prefix . '_wpagently_' . substr( str_replace( '-', '', wp_generate_uuid4() ), 0, 24 );
		$exists = 'group' === $prefix ? acf_get_field_group( $key ) : acf_get_field( $key );
		if ( ! $exists ) {
			return $key;
		}
	}
	throw new RuntimeException( 'Could not generate a unique ACF key.' );
}

/** @return string|WP_Error */
function wpagent_acf_group_acquire_lock( string $group_key ) {
	$option = 'wp_agent_acf_group_lock_' . hash( 'sha256', $group_key );
	$token  = wpagent_companion_acquire_option_lock( $option, 300 );
	if ( is_string( $token ) ) {
		return $token;
	}
	return new WP_Error( 'wpagent_acf_group_busy', __( 'Diese ACF-Feldgruppe wird gerade geändert. Versuche es gleich erneut.', 'wp-agent-companion' ), array( 'status' => 409 ) );
}

function wpagent_acf_group_release_lock( string $group_key, string $token ): void {
	wpagent_companion_release_option_lock( 'wp_agent_acf_group_lock_' . hash( 'sha256', $group_key ), $token );
}
