<?php
/** Shared, loss-aware helpers for native Pods content models. */

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

/** @return true|WP_Error */
function wpagent_pods_require_api() {
	if ( ! function_exists( 'pods_api' ) ) {
		return new WP_Error( 'wpagent_pods_missing', __( 'Pods 3.0 oder neuer ist nicht aktiv.', 'wp-agent-companion' ), array( 'status' => 409 ) );
	}
	$api = pods_api();
	foreach ( array( 'load_pod', 'load_pods', 'load_field', 'load_fields', 'save_pod', 'save_field', 'delete_pod', 'delete_field' ) as $method ) {
		if ( ! is_object( $api ) || ! is_callable( array( $api, $method ) ) ) {
			return new WP_Error( 'wpagent_pods_api_missing', __( 'Die benötigte native Pods-Konfigurations-API ist nicht verfügbar.', 'wp-agent-companion' ), array( 'status' => 409 ) );
		}
	}
	return true;
}

/** @return true|WP_Error */
function wpagent_pods_require_write_permission() {
	if ( function_exists( 'wpagent_perm_manage_options' ) && wpagent_perm_manage_options() ) {
		return true;
	}
	return new WP_Error( 'wpagent_forbidden', __( 'Keine Berechtigung.', 'wp-agent-companion' ), array( 'status' => 403 ) );
}

/**
 * Pods 3.3.x does not expose an atomic create-only configuration API.
 *
 * Its save methods resolve a definition by name before writing it. A lock held
 * by this plugin cannot protect against another Pods client between that read
 * and write, so creating a model or field would risk overwriting it.
 */
function wpagent_pods_provider_atomicity_unavailable( string $resource ): WP_Error {
	$labels = array(
		'model' => __( 'neue Pods-Modelle', 'wp-agent-companion' ),
		'field' => __( 'neue Pods-Felder', 'wp-agent-companion' ),
	);
	$label = $labels[ $resource ] ?? __( 'neue Pods-Definitionen', 'wp-agent-companion' );
	return new WP_Error(
		'wpagent_pods_provider_atomicity_unavailable',
		sprintf(
			/* translators: %s: blocked Pods configuration resource. */
			__( 'Pods 3.3 stellt für %s keine atomare Create-only-API bereit. WPAgently führt diesen Schreibvorgang deshalb nicht aus.', 'wp-agent-companion' ),
			$label
		),
		array( 'status' => 409, 'resource' => $resource )
	);
}

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

/** @return string|WP_Error */
function wpagent_pods_text( $value, string $label, int $max, bool $allow_empty = false ) {
	$text = is_string( $value ) ? trim( $value ) : '';
	if ( ( ! $allow_empty && '' === $text ) || wpagent_pods_text_length( $text ) > $max || sanitize_text_field( $text ) !== $text ) {
		/* translators: 1: input field name, 2: maximum number of characters. */
		return new WP_Error( 'wpagent_pods_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 bool|WP_Error */
function wpagent_pods_boolean( $value, string $label ) {
	if ( is_bool( $value ) ) {
		return $value;
	}
	if ( in_array( $value, array( 0, '0', 'false', '' ), true ) ) {
		return false;
	}
	if ( in_array( $value, array( 1, '1', 'true' ), true ) ) {
		return true;
	}
	/* translators: %s: input field name. */
	return new WP_Error( 'wpagent_pods_boolean', sprintf( __( '%s muss ein boolescher Wert sein.', 'wp-agent-companion' ), $label ), array( 'status' => 400 ) );
}

/** @return string|WP_Error */
function wpagent_pods_name( $value, ?string $kind = null ) {
	$name = is_string( $value ) ? $value : '';
	$max  = 'post_type' === $kind ? 20 : ( 'taxonomy' === $kind ? 32 : 64 );
	if ( '' === $name || strlen( $name ) > $max || ! preg_match( '/^[a-z][a-z0-9_]*$/', $name ) ) {
		/* translators: %d: maximum identifier length. */
		return new WP_Error( 'wpagent_pods_name', sprintf( __( 'Der Pods-Bezeichner muss mit einem Kleinbuchstaben beginnen und darf höchstens %d Zeichen aus a-z, 0-9 und Unterstrich enthalten.', 'wp-agent-companion' ), $max ), array( 'status' => 400 ) );
	}
	return $name;
}

/** @return string|WP_Error */
function wpagent_pods_rewrite_slug( $value ) {
	$slug = is_string( $value ) ? trim( $value ) : '';
	if ( '' !== $slug && ( strlen( $slug ) > 200 || sanitize_title( $slug ) !== $slug ) ) {
		return new WP_Error( 'wpagent_pods_rewrite', __( 'rewrite_slug muss leer oder ein gültiger URL-Slug mit höchstens 200 Zeichen sein.', 'wp-agent-companion' ), array( 'status' => 400 ) );
	}
	return $slug;
}

/** @return string[] */
function wpagent_pods_supported_field_types(): array {
	return array( 'text', 'website', 'phone', 'email', 'paragraph', 'wysiwyg', 'datetime', 'date', 'time', 'number', 'currency', 'file', 'avatar', 'oembed', 'pick', 'boolean', 'color' );
}

/** @return string[] */
function wpagent_pods_supported_post_supports(): array {
	return array( 'title', 'editor', 'author', 'thumbnail', 'excerpt', 'trackbacks', 'custom-fields', 'comments', 'revisions', 'page-attributes' );
}

/** @return mixed */
function wpagent_pods_canonicalize( $value ) {
	if ( is_object( $value ) ) {
		if ( is_callable( array( $value, 'get_args' ) ) ) {
			$value = $value->get_args();
		} else {
			$value = get_object_vars( $value );
		}
	}
	if ( ! is_array( $value ) ) {
		return $value;
	}
	if ( ! array_is_list( $value ) ) {
		ksort( $value, SORT_STRING );
	}
	foreach ( $value as $key => $item ) {
		$value[ $key ] = wpagent_pods_canonicalize( $item );
	}
	return $value;
}

function wpagent_pods_hash( $value ): string {
	$value   = wpagent_pods_canonicalize( $value );
	$encoded = wp_json_encode( $value, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE );
	return hash( 'sha256', false === $encoded ? serialize( $value ) : $encoded );
}

/** @return object|WP_Error */
function wpagent_pods_validate_model_object( $pod ) {
	if ( ! is_object( $pod ) || ! is_callable( array( $pod, 'get_args' ) ) || ! is_callable( array( $pod, 'get_id' ) ) ) {
		return new WP_Error( 'wpagent_pods_not_found', __( 'Pods-Modell nicht gefunden.', 'wp-agent-companion' ), array( 'status' => 404 ) );
	}
	$args = $pod->get_args();
	if ( ! in_array( (string) ( $args['type'] ?? '' ), array( 'post_type', 'taxonomy' ), true ) || 'meta' !== (string) ( $args['storage'] ?? '' ) || '' !== (string) ( $args['object'] ?? '' ) ) {
		return new WP_Error( 'wpagent_pods_model_unsupported', __( 'Nur eigenständige Pods-Inhaltstypen und Taxonomien mit Metadaten-Speicherung werden verwaltet. Erweiterte WordPress-Objekte und eigene Tabellen bleiben unverändert.', 'wp-agent-companion' ), array( 'status' => 409 ) );
	}
	return $pod;
}

/** @return object|WP_Error */
function wpagent_pods_get_model( $name ) {
	$valid = wpagent_pods_name( $name );
	if ( is_wp_error( $valid ) ) {
		return $valid;
	}
	try {
		$pod = pods_api()->load_pod( array( 'name' => $valid, 'bypass_cache' => true ) );
	} catch ( Throwable $error ) {
		return new WP_Error( 'wpagent_pods_read_failed', __( 'Das Pods-Modell konnte nicht gelesen werden.', 'wp-agent-companion' ), array( 'status' => 500 ) );
	}
	return wpagent_pods_validate_model_object( $pod );
}

/** @return object|WP_Error */
function wpagent_pods_get_model_by_identity( int $id, string $name ) {
	$valid = wpagent_pods_name( $name );
	if ( is_wp_error( $valid ) ) {
		return $valid;
	}
	if ( $id < 1 ) {
		return new WP_Error( 'wpagent_pods_identity_conflict', __( 'Die Pods-Modell-ID ist nicht mehr gültig.', 'wp-agent-companion' ), array( 'status' => 409 ) );
	}
	try {
		$pod = pods_api()->load_pod( array( 'id' => $id, 'name' => $valid, 'bypass_cache' => true ) );
	} catch ( Throwable $error ) {
		return new WP_Error( 'wpagent_pods_read_failed', __( 'Das Pods-Modell konnte nicht erneut gelesen werden.', 'wp-agent-companion' ), array( 'status' => 500 ) );
	}
	$pod = wpagent_pods_validate_model_object( $pod );
	if ( is_wp_error( $pod ) ) {
		return $pod;
	}
	$args = $pod->get_args();
	if ( $id !== (int) $pod->get_id() || $valid !== (string) ( $args['name'] ?? '' ) ) {
		return new WP_Error( 'wpagent_pods_identity_conflict', __( 'Das Pods-Modell stimmt nicht mehr mit seiner gelesenen ID und seinem Namen überein.', 'wp-agent-companion' ), array( 'status' => 409 ) );
	}
	return $pod;
}

/**
 * Rechecks the native identity and complete state immediately before a mutation.
 *
 * Pods 3.3.x has no compare-and-swap or transaction for these definitions, so
 * this detects a stale target but cannot close a foreign write that races after
 * the final read and before Pods receives the mutation.
 *
 * @return array{pod:object,state:array<string,mixed>}|WP_Error
 */
function wpagent_pods_revalidate_model_target( int $id, string $name, string $expected_hash ) {
	$permission = wpagent_pods_require_write_permission();
	if ( is_wp_error( $permission ) ) {
		return $permission;
	}
	$pod = wpagent_pods_get_model_by_identity( $id, $name );
	if ( is_wp_error( $pod ) ) {
		if ( 'wpagent_pods_read_failed' === $pod->get_error_code() ) {
			return $pod;
		}
		return new WP_Error( 'wpagent_pods_conflict', __( 'Das Pods-Modell wurde seit dem Lesen gelöscht oder verändert.', 'wp-agent-companion' ), array( 'status' => 409 ) );
	}
	try {
		$state = wpagent_pods_model_state( $pod );
	} catch ( Throwable $error ) {
		return new WP_Error( 'wpagent_pods_read_failed', __( 'Der aktuelle Zustand des Pods-Modells konnte nicht vollständig gelesen werden.', 'wp-agent-companion' ), array( 'status' => 500 ) );
	}
	if ( ! preg_match( '/^[a-f0-9]{64}$/', $expected_hash ) || ! hash_equals( $state['hash'], $expected_hash ) ) {
		return new WP_Error( 'wpagent_pods_conflict', __( 'Das Pods-Modell wurde seit dem Lesen verändert oder der Konflikt-Hash ist ungültig.', 'wp-agent-companion' ), array( 'status' => 409, 'current_hash' => $state['hash'] ) );
	}
	return array( 'pod' => $pod, 'state' => $state );
}

/** @return bool|WP_Error */
function wpagent_pods_model_is_absent( int $id, string $name ) {
	$valid = wpagent_pods_name( $name );
	if ( is_wp_error( $valid ) ) {
		return $valid;
	}
	try {
		$by_id = pods_api()->load_pod( array( 'id' => $id, 'bypass_cache' => true ) );
		$by_name = pods_api()->load_pod( array( 'name' => $valid, 'bypass_cache' => true ) );
	} catch ( Throwable $error ) {
		return new WP_Error( 'wpagent_pods_read_failed', __( 'Der Löschzustand des Pods-Modells konnte nicht gelesen werden.', 'wp-agent-companion' ), array( 'status' => 500 ) );
	}
	return ! is_object( $by_id ) && ! is_object( $by_name );
}

/**
 * Loads every field or fails closed when Pods returns an incomplete result.
 *
 * @return array<int,object>
 * @throws RuntimeException When Pods cannot provide a complete field list.
 */
function wpagent_pods_model_fields( object $pod ): array {
	try {
		$fields = pods_api()->load_fields( array( 'pod_id' => (int) $pod->get_id(), 'bypass_cache' => true ) );
	} catch ( Throwable $error ) {
		throw new RuntimeException( 'Pods fields could not be read.', 0, $error );
	}
	if ( ! is_array( $fields ) ) {
		throw new RuntimeException( 'Pods fields could not be read.' );
	}
	foreach ( $fields as $field ) {
		if ( ! is_object( $field ) || ! is_callable( array( $field, 'get_args' ) ) ) {
			throw new RuntimeException( 'Pods returned an incomplete field list.' );
		}
	}
	$fields = array_values( $fields );
	usort( $fields, static function ( object $a, object $b ): int {
		$a_args = is_callable( array( $a, 'get_args' ) ) ? $a->get_args() : array();
		$b_args = is_callable( array( $b, 'get_args' ) ) ? $b->get_args() : array();
		$weight = (int) ( $a_args['weight'] ?? 0 ) <=> (int) ( $b_args['weight'] ?? 0 );
		return 0 !== $weight ? $weight : strcasecmp( (string) ( $a_args['name'] ?? '' ), (string) ( $b_args['name'] ?? '' ) );
	} );
	return $fields;
}

function wpagent_pods_relationship_table(): ?string {
	static $resolved = false;
	if ( false !== $resolved ) {
		return $resolved;
	}
	global $wpdb;
	$table  = $wpdb->prefix . 'podsrel';
	$exists = $wpdb->get_var( $wpdb->prepare( 'SHOW TABLES LIKE %s', $wpdb->esc_like( $table ) ) );
	$resolved = $table === $exists ? $table : null;
	return $resolved;
}

function wpagent_pods_relationship_count( int $pod_id, int $field_id = 0 ): int {
	global $wpdb;
	$table = wpagent_pods_relationship_table();
	if ( null === $table ) {
		return 0;
	}
	if ( $field_id > 0 ) {
		return (int) $wpdb->get_var( $wpdb->prepare( "SELECT COUNT(*) FROM {$table} WHERE (pod_id = %d AND field_id = %d) OR (related_pod_id = %d AND related_field_id = %d)", $pod_id, $field_id, $pod_id, $field_id ) );
	}
	return (int) $wpdb->get_var( $wpdb->prepare( "SELECT COUNT(*) FROM {$table} WHERE pod_id = %d OR related_pod_id = %d", $pod_id, $pod_id ) );
}

function wpagent_pods_content_count( string $kind, string $name ): int {
	global $wpdb;
	if ( 'post_type' === $kind ) {
		return (int) $wpdb->get_var( $wpdb->prepare( "SELECT COUNT(*) FROM {$wpdb->posts} WHERE post_type = %s AND post_status NOT IN ('auto-draft','inherit')", $name ) );
	}
	return (int) $wpdb->get_var( $wpdb->prepare( "SELECT COUNT(DISTINCT term_id) FROM {$wpdb->term_taxonomy} WHERE taxonomy = %s", $name ) );
}

function wpagent_pods_field_value_count( string $kind, string $model_name, string $field_name ): int {
	global $wpdb;
	if ( 'post_type' === $kind ) {
		return (int) $wpdb->get_var( $wpdb->prepare( "SELECT COUNT(pm.meta_id) FROM {$wpdb->postmeta} pm INNER JOIN {$wpdb->posts} p ON p.ID = pm.post_id WHERE p.post_type = %s AND pm.meta_key = %s", $model_name, $field_name ) );
	}
	return (int) $wpdb->get_var( $wpdb->prepare( "SELECT COUNT(DISTINCT tm.meta_id) FROM {$wpdb->termmeta} tm INNER JOIN {$wpdb->term_taxonomy} tt ON tt.term_id = tm.term_id WHERE tt.taxonomy = %s AND tm.meta_key = %s", $model_name, $field_name ) );
}

/** @return string[] */
function wpagent_pods_unmanaged_model_options( array $args ): array {
	$known = array_flip( array(
		'object_type', 'object_storage_type', 'id', 'name', 'label', 'label_singular', 'description', 'type', 'storage', 'object', 'alias',
		'public', 'show_ui', 'rest_enable', 'hierarchical', 'has_archive', 'rewrite_custom_slug', 'show_admin_column',
	) );
	foreach ( wpagent_pods_supported_post_supports() as $support ) {
		$known[ 'supports_' . str_replace( '-', '_', $support ) ] = true;
	}
	$unmanaged = array();
	foreach ( array_keys( $args ) as $key ) {
		if ( isset( $known[ $key ] ) || str_starts_with( $key, 'built_in_post_types_' ) || str_starts_with( $key, 'built_in_taxonomies_' ) ) {
			continue;
		}
		$unmanaged[] = (string) $key;
	}
	sort( $unmanaged, SORT_STRING );
	return $unmanaged;
}

/** @return string[] */
function wpagent_pods_unmanaged_field_options( array $args ): array {
	$known = array_flip( array(
		'object_type', 'object_storage_type', 'id', 'parent', 'group', 'name', 'label', 'description', 'weight', 'type', 'required', 'repeatable',
		'pick_object', 'pick_val', 'pick_format_type', 'pick_format_single', 'pick_format_multi',
		'file_format_type', 'file_type', 'avatar_format_type', 'avatar_type',
	) );
	$unmanaged = array_values( array_diff( array_keys( $args ), array_keys( $known ) ) );
	sort( $unmanaged, SORT_STRING );
	return array_map( 'strval', $unmanaged );
}

/**
 * Build the portion of the model state that participates in conflict hashes.
 *
 * Pods can materialize a small, provider-defined set of disabled model flags
 * as the string "0" when a definition is saved, even when those flags were
 * absent from the original definition.  Treating those exact values as the
 * absent default keeps a read-save-read cycle stable.  Every other provider
 * key and value remains hash-bound, including unmanaged options.
 *
 * @param array<string,mixed> $args
 * @param array<string,mixed> $fields
 * @return array{model:array<string,mixed>,fields:array<string,mixed>}
 */
function wpagent_pods_model_hash_material( array $args, array $fields ): array {
	$zero_defaults = array(
		'hierarchical',
		'has_archive',
		'supports_editor',
		'supports_author',
		'supports_thumbnail',
		'supports_excerpt',
		'supports_trackbacks',
		'supports_custom_fields',
		'supports_comments',
		'supports_revisions',
		'supports_page_attributes',
	);
	$model = $args;
	foreach ( $zero_defaults as $key ) {
		if ( ! array_key_exists( $key, $model ) ) {
			continue;
		}
		$value = $model[ $key ];
		if ( 0 === $value || '0' === $value || false === $value ) {
			unset( $model[ $key ] );
		}
	}
	return array(
		'model'  => $model,
		'fields' => $fields,
	);
}

/**
 * Keep every native field argument hash-bound, including provider-specific
 * and unmanaged values.  This explicit boundary prevents model defaults from
 * being applied to field state accidentally.
 *
 * @param array<string,mixed> $args
 * @return array<string,mixed>
 */
function wpagent_pods_field_hash_material( array $args ): array {
	return $args;
}

/** @return array<string,mixed> */
function wpagent_pods_field_state( object $pod, object $field ): array {
	$pod_args   = $pod->get_args();
	$field_args = $field->get_args();
	$type       = (string) ( $field_args['type'] ?? 'text' );
	$multiple   = false;
	if ( 'pick' === $type ) {
		$multiple = 'multi' === (string) ( $field_args['pick_format_type'] ?? 'single' );
	} elseif ( in_array( $type, array( 'file', 'avatar' ), true ) ) {
		$multiple = 'multi' === (string) ( $field_args[ $type . '_format_type' ] ?? 'single' );
	}
	$relationship_object = 'pick' === $type ? (string) ( $field_args['pick_object'] ?? '' ) : ( in_array( $type, array( 'file', 'avatar' ), true ) ? 'media' : '' );
	$relationship_target = 'pick' === $type ? (string) ( $field_args['pick_val'] ?? '' ) : ( in_array( $type, array( 'file', 'avatar' ), true ) ? 'attachment' : '' );
	return array(
		'id'                  => (int) ( $field_args['id'] ?? 0 ),
		'name'                => (string) ( $field_args['name'] ?? '' ),
		'label'               => (string) ( $field_args['label'] ?? '' ),
		'description'         => (string) ( $field_args['description'] ?? '' ),
		'type'                => $type,
		'required'            => (bool) ( $field_args['required'] ?? false ),
		'repeatable'          => (bool) ( $field_args['repeatable'] ?? false ),
		'multiple'            => $multiple,
		'weight'              => (int) ( $field_args['weight'] ?? 0 ),
		'relationship_object' => $relationship_object,
		'relationship_target' => $relationship_target,
		'file_type'           => in_array( $type, array( 'file', 'avatar' ), true ) ? (string) ( $field_args[ $type . '_type' ] ?? 'images' ) : '',
		'value_count'         => wpagent_pods_field_value_count( (string) $pod_args['type'], (string) $pod_args['name'], (string) $field_args['name'] ),
		'relationship_count'  => wpagent_pods_relationship_count( (int) $pod->get_id(), (int) $field->get_id() ),
		'unmanaged_options'   => wpagent_pods_unmanaged_field_options( $field_args ),
		'hash'                => wpagent_pods_hash( wpagent_pods_field_hash_material( $field_args ) ),
	);
}

/** @return array<string,mixed> */
function wpagent_pods_model_state( object $pod ): array {
	$args   = $pod->get_args();
	$kind   = (string) $args['type'];
	$name   = (string) $args['name'];
	$fields = wpagent_pods_model_fields( $pod );
	$supports = array();
	$taxonomies = array();
	$object_types = array();
	foreach ( wpagent_pods_supported_post_supports() as $support ) {
		if ( ! empty( $args[ 'supports_' . str_replace( '-', '_', $support ) ] ) ) {
			$supports[] = $support;
		}
	}
	foreach ( $args as $key => $value ) {
		if ( ! empty( $value ) && str_starts_with( $key, 'built_in_taxonomies_' ) ) {
			$taxonomies[] = substr( $key, strlen( 'built_in_taxonomies_' ) );
		} elseif ( ! empty( $value ) && str_starts_with( $key, 'built_in_post_types_' ) ) {
			$object_types[] = substr( $key, strlen( 'built_in_post_types_' ) );
		}
	}
	sort( $supports, SORT_STRING );
	sort( $taxonomies, SORT_STRING );
	sort( $object_types, SORT_STRING );
	$runtime = 'post_type' === $kind ? get_post_type_object( $name ) : get_taxonomy( $name );
	$provider = is_object( $runtime ) ? (string) ( $runtime->_provider ?? '' ) : '';
	$field_material = array();
	foreach ( $fields as $field ) {
		$field_args = $field->get_args();
		$field_material[ (string) $field_args['name'] ] = wpagent_pods_field_hash_material( $field_args );
	}
	ksort( $field_material, SORT_STRING );
	return array(
		'id'                    => (int) $pod->get_id(),
		'name'                  => $name,
		'kind'                  => $kind,
		'label'                 => (string) ( $args['label'] ?? $name ),
		'singular_label'        => (string) ( $args['label_singular'] ?? $args['label'] ?? $name ),
		'description'           => (string) ( $args['description'] ?? '' ),
		'storage'               => (string) ( $args['storage'] ?? '' ),
		'public'                => (bool) ( $args['public'] ?? true ),
		'hierarchical'          => (bool) ( $args['hierarchical'] ?? false ),
		'show_ui'               => (bool) ( $args['show_ui'] ?? true ),
		'show_in_rest'          => (bool) ( $args['rest_enable'] ?? false ),
		'has_archive'           => 'post_type' === $kind && (bool) ( $args['has_archive'] ?? false ),
		'show_admin_column'     => 'taxonomy' === $kind && (bool) ( $args['show_admin_column'] ?? false ),
		'rewrite_slug'          => (string) ( $args['rewrite_custom_slug'] ?? '' ),
		'supports'              => $supports,
		'taxonomies'            => $taxonomies,
		'object_types'           => $object_types,
		'fields'                => array_map( static fn( object $field ): array => wpagent_pods_field_state( $pod, $field ), $fields ),
		'field_count'           => count( $fields ),
		'content_count'         => wpagent_pods_content_count( $kind, $name ),
		'relationship_count'    => wpagent_pods_relationship_count( (int) $pod->get_id() ),
		'runtime_registered'    => 'pods' === $provider,
		'registration_conflict' => is_object( $runtime ) && 'pods' !== $provider,
		'unmanaged_options'     => wpagent_pods_unmanaged_model_options( $args ),
		'hash'                  => wpagent_pods_hash( wpagent_pods_model_hash_material( $args, $field_material ) ),
	);
}

/** @return string|WP_Error */
function wpagent_pods_acquire_lock( string $name ) {
	$option = 'wp_agent_pods_model_lock_' . hash( 'sha256', $name );
	$token  = wpagent_companion_acquire_option_lock( $option, 300 );
	if ( is_string( $token ) ) {
		return $token;
	}
	return new WP_Error( 'wpagent_pods_busy', __( 'Dieses Pods-Modell wird gerade geändert. Versuche es gleich erneut.', 'wp-agent-companion' ), array( 'status' => 409 ) );
}

function wpagent_pods_release_lock( string $name, string $token ): void {
	wpagent_companion_release_option_lock( 'wp_agent_pods_model_lock_' . hash( 'sha256', $name ), $token );
}

/** @return array<string,mixed> */
function wpagent_pods_field_output_schema(): array {
	$properties = array(
		'id' => array( 'type' => 'integer' ), 'name' => array( 'type' => 'string' ), 'label' => array( 'type' => 'string' ),
		'description' => array( 'type' => 'string' ), 'type' => array( 'type' => 'string' ), 'required' => array( 'type' => 'boolean' ),
		'repeatable' => array( 'type' => 'boolean' ), 'multiple' => array( 'type' => 'boolean' ), 'weight' => array( 'type' => 'integer' ),
		'relationship_object' => array( 'type' => 'string' ), 'relationship_target' => array( 'type' => 'string' ), 'file_type' => array( 'type' => 'string' ),
		'value_count' => array( 'type' => 'integer' ), 'relationship_count' => array( 'type' => 'integer' ),
		'unmanaged_options' => array( 'type' => 'array', 'items' => array( 'type' => 'string' ) ), 'hash' => array( 'type' => 'string' ),
	);
	return array( 'type' => 'object', 'properties' => $properties, 'required' => array_keys( $properties ), 'additionalProperties' => false );
}

/** @return array<string,mixed> */
function wpagent_pods_model_output_schema(): array {
	$properties = array(
		'id' => array( 'type' => 'integer' ), 'name' => array( 'type' => 'string' ), 'kind' => array( 'type' => 'string', 'enum' => array( 'post_type', 'taxonomy' ) ),
		'label' => array( 'type' => 'string' ), 'singular_label' => array( 'type' => 'string' ), 'description' => array( 'type' => 'string' ),
		'storage' => array( 'type' => 'string' ), 'public' => array( 'type' => 'boolean' ), 'hierarchical' => array( 'type' => 'boolean' ),
		'show_ui' => array( 'type' => 'boolean' ), 'show_in_rest' => array( 'type' => 'boolean' ), 'has_archive' => array( 'type' => 'boolean' ),
		'show_admin_column' => array( 'type' => 'boolean' ), 'rewrite_slug' => array( 'type' => 'string' ),
		'supports' => array( 'type' => 'array', 'items' => array( 'type' => 'string' ) ), 'taxonomies' => array( 'type' => 'array', 'items' => array( 'type' => 'string' ) ),
		'object_types' => array( 'type' => 'array', 'items' => array( 'type' => 'string' ) ), 'fields' => array( 'type' => 'array', 'items' => wpagent_pods_field_output_schema() ),
		'field_count' => array( 'type' => 'integer' ), 'content_count' => array( 'type' => 'integer' ), 'relationship_count' => array( 'type' => 'integer' ),
		'runtime_registered' => array( 'type' => 'boolean' ), 'registration_conflict' => array( 'type' => 'boolean' ),
		'unmanaged_options' => array( 'type' => 'array', 'items' => array( 'type' => 'string' ) ), 'hash' => array( 'type' => 'string' ),
	);
	return array( 'type' => 'object', 'properties' => $properties, 'required' => array_keys( $properties ), 'additionalProperties' => false );
}
