<?php
/** Shared helpers for persistent Custom Post Type UI definitions. */

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

/** @return array<string,array<string,mixed>> */
function wpagent_cptui_kinds(): array {
	return array(
		'post_type' => array(
			'option' => 'cptui_post_types',
			'getter' => 'cptui_get_post_type_data',
			'reserved' => 'cptui_reserved_post_types',
			'max_slug' => 20,
		),
		'taxonomy' => array(
			'option' => 'cptui_taxonomies',
			'getter' => 'cptui_get_taxonomy_data',
			'reserved' => 'cptui_reserved_taxonomies',
			'max_slug' => 32,
		),
	);
}

/** @return true|WP_Error */
function wpagent_cptui_require_provider() {
	$required = array(
		'cptui_get_post_type_data', 'cptui_get_taxonomy_data',
		'cptui_register_single_post_type', 'cptui_register_single_taxonomy',
		'cptui_reserved_post_types', 'cptui_reserved_taxonomies',
		'cptui_check_page_slugs',
	);
	foreach ( $required as $function ) {
		if ( ! function_exists( $function ) ) {
			return new WP_Error( 'wpagent_cptui_missing', __( 'Custom Post Type UI 1.19.3 oder neuer ist nicht aktiv oder seine Definitions-API ist nicht verfügbar.', 'wp-agent-companion' ), array( 'status' => 409 ) );
		}
	}
	if ( ! defined( 'CPTUI_VERSION' ) || version_compare( (string) CPTUI_VERSION, '1.19.3', '<' ) ) {
		return new WP_Error( 'wpagent_cptui_version', __( 'Für diese Integration wird Custom Post Type UI 1.19.3 oder neuer benötigt.', 'wp-agent-companion' ), array( 'status' => 409 ) );
	}
	return true;
}

/** @return mixed */
function wpagent_cptui_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_cptui_canonicalize( $item );
	}
	return $value;
}

function wpagent_cptui_safe_stored_value( $value, int $depth, int &$nodes ): bool {
	++$nodes;
	if ( $nodes > 100000 || $depth > 32 ) {
		return false;
	}
	if ( null === $value || is_string( $value ) || is_int( $value ) || is_bool( $value ) ) {
		return true;
	}
	if ( is_float( $value ) ) {
		return is_finite( $value );
	}
	if ( ! is_array( $value ) ) {
		return false;
	}
	foreach ( $value as $key => $item ) {
		if ( ! is_int( $key ) && ! is_string( $key ) ) {
			return false;
		}
		if ( ! wpagent_cptui_safe_stored_value( $item, $depth + 1, $nodes ) ) {
			return false;
		}
	}
	return true;
}

/** @return true|WP_Error */
function wpagent_cptui_validate_stored_definition( string $kind, string $slug, array $definition ) {
	$config = wpagent_cptui_kinds()[ $kind ];
	if ( strlen( $slug ) > $config['max_slug'] || sanitize_key( $slug ) !== $slug || ! preg_match( '/^[a-z0-9_-]+$/', $slug ) ) {
		return new WP_Error( 'wpagent_cptui_storage', __( 'Eine gespeicherte CPT-UI-Definition besitzt einen ungültigen Slug und wird deshalb nicht verarbeitet.', 'wp-agent-companion' ), array( 'status' => 500 ) );
	}
	if ( ! isset( $definition['name'] ) || ! is_string( $definition['name'] ) || $definition['name'] !== $slug ) {
		return new WP_Error( 'wpagent_cptui_storage', __( 'Eine gespeicherte CPT-UI-Definition besitzt keinen eindeutigen Slug und wird deshalb nicht verarbeitet.', 'wp-agent-companion' ), array( 'status' => 500 ) );
	}
	foreach ( array( 'label', 'singular_label', 'description', 'rewrite_slug' ) as $field ) {
		if ( isset( $definition[ $field ] ) && ! is_string( $definition[ $field ] ) ) {
			return new WP_Error( 'wpagent_cptui_storage', __( 'Eine gespeicherte CPT-UI-Definition enthält ungültige Textfelder und wird deshalb nicht verarbeitet.', 'wp-agent-companion' ), array( 'status' => 500 ) );
		}
	}
	foreach ( array( 'supports', 'taxonomies', 'object_types' ) as $field ) {
		if ( ! isset( $definition[ $field ] ) ) {
			continue;
		}
		if ( ! is_array( $definition[ $field ] ) || ! array_is_list( $definition[ $field ] ) ) {
			return new WP_Error( 'wpagent_cptui_storage', __( 'Eine gespeicherte CPT-UI-Definition enthält ungültige Zuordnungslisten und wird deshalb nicht verarbeitet.', 'wp-agent-companion' ), array( 'status' => 500 ) );
		}
		foreach ( $definition[ $field ] as $item ) {
			if ( ! is_string( $item ) ) {
				return new WP_Error( 'wpagent_cptui_storage', __( 'Eine gespeicherte CPT-UI-Definition enthält ungültige Zuordnungslisten und wird deshalb nicht verarbeitet.', 'wp-agent-companion' ), array( 'status' => 500 ) );
			}
		}
	}
	$nodes = 0;
	if ( ! wpagent_cptui_safe_stored_value( $definition, 0, $nodes ) ) {
		return new WP_Error( 'wpagent_cptui_storage', __( 'Eine gespeicherte CPT-UI-Definition ist zu tief verschachtelt oder enthält unsichere Werte.', 'wp-agent-companion' ), array( 'status' => 500 ) );
	}
	return true;
}

/** @return array<string,array<string,mixed>>|WP_Error */
function wpagent_cptui_option_data( string $kind ) {
	$config = wpagent_cptui_kinds()[ $kind ] ?? null;
	if ( ! is_array( $config ) ) {
		return new WP_Error( 'wpagent_cptui_kind', __( 'Unbekannte CPT-UI-Definitionsart.', 'wp-agent-companion' ), array( 'status' => 500 ) );
	}
	$value = get_option( $config['option'], array() );
	if ( ! is_array( $value ) ) {
		return new WP_Error( 'wpagent_cptui_storage', __( 'Die gespeicherten CPT-UI-Definitionen sind beschädigt und werden deshalb nicht verändert.', 'wp-agent-companion' ), array( 'status' => 500 ) );
	}
	if ( count( $value ) > 500 ) {
		return new WP_Error( 'wpagent_cptui_storage', __( 'Die gespeicherten CPT-UI-Definitionen überschreiten die verarbeitbare Sicherheitsgrenze.', 'wp-agent-companion' ), array( 'status' => 500 ) );
	}
	foreach ( $value as $slug => $definition ) {
		if ( ! is_string( $slug ) || ! is_array( $definition ) ) {
			return new WP_Error( 'wpagent_cptui_storage', __( 'Die gespeicherten CPT-UI-Definitionen sind beschädigt und werden deshalb nicht verändert.', 'wp-agent-companion' ), array( 'status' => 500 ) );
		}
		foreach ( array_keys( $definition ) as $field ) {
			if ( ! is_string( $field ) ) {
				return new WP_Error( 'wpagent_cptui_storage', __( 'Eine gespeicherte CPT-UI-Definition enthält ungültige Feldnamen und wird deshalb nicht verändert.', 'wp-agent-companion' ), array( 'status' => 500 ) );
			}
		}
		$valid = wpagent_cptui_validate_stored_definition( $kind, $slug, $definition );
		if ( is_wp_error( $valid ) ) {
			return $valid;
		}
	}
	if ( strlen( maybe_serialize( $value ) ) > 2 * MB_IN_BYTES ) {
		return new WP_Error( 'wpagent_cptui_storage', __( 'Die gespeicherten CPT-UI-Definitionen überschreiten die verarbeitbare Sicherheitsgrenze.', 'wp-agent-companion' ), array( 'status' => 500 ) );
	}
	return $value;
}

function wpagent_cptui_option_hash( array $definitions ): string {
	$material = wpagent_cptui_canonicalize( $definitions );
	$encoded = wp_json_encode( $material, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE );
	return hash( 'sha256', false === $encoded ? serialize( $material ) : $encoded );
}

function wpagent_cptui_bool( $value ): bool {
	if ( is_array( $value ) || is_object( $value ) || is_resource( $value ) ) {
		return false;
	}
	$text = (string) $value;
	return '' !== $text && '0' !== $text && 'false' !== $text;
}

function wpagent_cptui_display_text( $value ): string {
	return is_string( $value ) ? html_entity_decode( $value, ENT_QUOTES | ENT_HTML5, 'UTF-8' ) : '';
}

/** @return string|WP_Error */
function wpagent_cptui_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_cptui_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 string|WP_Error */
function wpagent_cptui_slug( $value, string $kind, bool $allow_reserved = false ) {
	$config = wpagent_cptui_kinds()[ $kind ];
	$slug = is_string( $value ) ? $value : '';
	if ( '' === $slug || strlen( $slug ) > $config['max_slug'] || sanitize_key( $slug ) !== $slug || ! preg_match( '/^[a-z0-9_-]+$/', $slug ) ) {
		/* translators: %d: maximum slug length in characters. */
		return new WP_Error( 'wpagent_cptui_slug', sprintf( __( 'Der Slug darf nur Kleinbuchstaben, Ziffern, Bindestriche und Unterstriche enthalten und höchstens %d Zeichen lang sein.', 'wp-agent-companion' ), $config['max_slug'] ), array( 'status' => 400 ) );
	}
	$reserved = call_user_func( $config['reserved'] );
	if ( ! $allow_reserved && is_array( $reserved ) && in_array( $slug, $reserved, true ) ) {
		return new WP_Error( 'wpagent_cptui_reserved', __( 'Dieser Slug ist für WordPress oder CPT UI reserviert.', 'wp-agent-companion' ), array( 'status' => 400 ) );
	}
	return $slug;
}

/** @return string|WP_Error */
function wpagent_cptui_rewrite_slug( $value ) {
	$slug = is_string( $value ) ? trim( $value ) : '';
	if ( '' !== $slug && ( strlen( $slug ) > 200 || sanitize_title( $slug ) !== $slug ) ) {
		return new WP_Error( 'wpagent_cptui_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[]|WP_Error */
function wpagent_cptui_supports( $value ) {
	$allowed = array( 'title', 'editor', 'author', 'thumbnail', 'excerpt', 'comments', 'trackbacks', 'revisions', 'page-attributes', 'custom-fields', 'post-formats' );
	if ( ! is_array( $value ) || ! array_is_list( $value ) || count( $value ) > count( $allowed ) ) {
		return new WP_Error( 'wpagent_cptui_supports', __( 'supports muss eine Liste unterstützter WordPress-Funktionen sein.', 'wp-agent-companion' ), array( 'status' => 400 ) );
	}
	$result = array();
	foreach ( $value as $support ) {
		if ( ! is_string( $support ) || ! in_array( $support, $allowed, true ) ) {
			return new WP_Error( 'wpagent_cptui_support', __( 'supports enthält eine nicht freigegebene WordPress-Funktion.', 'wp-agent-companion' ), array( 'status' => 400 ) );
		}
		$result[] = $support;
	}
	$result = array_values( array_unique( $result ) );
	sort( $result, SORT_STRING );
	return $result;
}

/** @return string[]|WP_Error */
function wpagent_cptui_object_names( $value, string $kind, bool $allow_empty = false ) {
	if ( ! is_array( $value ) || ! array_is_list( $value ) || ( ! $allow_empty && array() === $value ) || count( $value ) > 20 ) {
		return new WP_Error( 'wpagent_cptui_objects', __( 'Die Zuordnung muss eine Liste mit höchstens 20 registrierten Objekten enthalten.', 'wp-agent-companion' ), array( 'status' => 400 ) );
	}
	$other_kind = 'post_type' === $kind ? 'taxonomy' : 'post_type';
	$stored = wpagent_cptui_option_data( $other_kind );
	if ( is_wp_error( $stored ) ) {
		return $stored;
	}
	$result = array();
	foreach ( $value as $name ) {
		if ( ! is_string( $name ) || sanitize_key( $name ) !== $name ) {
			return new WP_Error( 'wpagent_cptui_object', __( 'Eine Zuordnung ist nicht registriert oder ungültig.', 'wp-agent-companion' ), array( 'status' => 400 ) );
		}
		$registered = 'post_type' === $kind ? taxonomy_exists( $name ) : post_type_exists( $name );
		if ( ! $registered && ! isset( $stored[ $name ] ) ) {
			return new WP_Error( 'wpagent_cptui_object', __( 'Eine Zuordnung ist nicht registriert oder ungültig.', 'wp-agent-companion' ), array( 'status' => 400 ) );
		}
		$result[] = $name;
	}
	$result = array_values( array_unique( $result ) );
	sort( $result, SORT_STRING );
	return $result;
}

/** @return int|WP_Error */
function wpagent_cptui_content_count( string $kind, string $slug ) {
	global $wpdb;
	if ( 'post_type' === $kind ) {
		$count = $wpdb->get_var( $wpdb->prepare( "SELECT COUNT(*) FROM {$wpdb->posts} WHERE post_type = %s AND post_status NOT IN ('auto-draft','inherit')", $slug ) );
	} else {
		$count = $wpdb->get_var( $wpdb->prepare( "SELECT COUNT(DISTINCT term_id) FROM {$wpdb->term_taxonomy} WHERE taxonomy = %s", $slug ) );
	}
	if ( '' !== $wpdb->last_error || null === $count ) {
		return new WP_Error( 'wpagent_cptui_count', __( 'Die gespeicherten Inhalte der CPT-UI-Definition konnten nicht sicher gezählt werden.', 'wp-agent-companion' ), array( 'status' => 500 ) );
	}
	return (int) $count;
}

/** @param string[] $slugs
 *  @return array<string,int>|WP_Error
 */
function wpagent_cptui_content_counts( string $kind, array $slugs ) {
	if ( array() === $slugs ) {
		return array();
	}
	global $wpdb;
	$placeholders = implode( ', ', array_fill( 0, count( $slugs ), '%s' ) );
	if ( 'post_type' === $kind ) {
		$query = $wpdb->prepare( "SELECT post_type AS slug, COUNT(*) AS amount FROM {$wpdb->posts} WHERE post_type IN ($placeholders) AND post_status NOT IN ('auto-draft','inherit') GROUP BY post_type", ...$slugs ); // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
	} else {
		$query = $wpdb->prepare( "SELECT taxonomy AS slug, COUNT(DISTINCT term_id) AS amount FROM {$wpdb->term_taxonomy} WHERE taxonomy IN ($placeholders) GROUP BY taxonomy", ...$slugs ); // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
	}
	$counts = array_fill_keys( $slugs, 0 );
	$rows = $wpdb->get_results( $query, ARRAY_A );
	if ( '' !== $wpdb->last_error || ! is_array( $rows ) ) {
		return new WP_Error( 'wpagent_cptui_count', __( 'Die gespeicherten Inhalte der CPT-UI-Definitionen konnten nicht sicher gezählt werden.', 'wp-agent-companion' ), array( 'status' => 500 ) );
	}
	foreach ( $rows as $row ) {
		if ( isset( $counts[ $row['slug'] ] ) ) {
			$counts[ $row['slug'] ] = (int) $row['amount'];
		}
	}
	return $counts;
}

/** @return string[] */
function wpagent_cptui_managed_keys( string $kind ): array {
	$common = array( 'name', 'label', 'singular_label', 'description', 'public', 'publicly_queryable', 'hierarchical', 'show_ui', 'show_in_rest', 'rewrite', 'rewrite_slug', 'rewrite_withfront', 'labels' );
	if ( 'post_type' === $kind ) {
		return array_merge( $common, array( 'show_in_nav_menus', 'delete_with_user', 'rest_base', 'rest_controller_class', 'rest_namespace', 'has_archive', 'has_archive_string', 'exclude_from_search', 'capability_type', 'can_export', 'query_var', 'query_var_slug', 'menu_position', 'show_in_menu', 'show_in_menu_string', 'menu_icon', 'register_meta_box_cb', 'supports', 'taxonomies', 'custom_supports', 'enter_title_here' ) );
	}
	return array_merge( $common, array( 'show_in_menu', 'show_in_nav_menus', 'query_var', 'query_var_slug', 'rewrite_hierarchical', 'show_admin_column', 'show_tagcloud', 'sort', 'show_in_quick_edit', 'rest_base', 'rest_controller_class', 'rest_namespace', 'meta_box_cb', 'default_term', 'object_types' ) );
}

function wpagent_cptui_runtime_matches( string $kind, array $state ): bool {
	$slug = $state['slug'];
	if ( 'post_type' === $kind ) {
		$object = get_post_type_object( $slug );
		if ( ! $object
			|| (bool) $object->public !== $state['public']
			|| (bool) $object->hierarchical !== $state['hierarchical']
			|| (bool) $object->show_ui !== $state['show_ui']
			|| (bool) $object->show_in_rest !== $state['show_in_rest']
			|| (bool) $object->has_archive !== $state['has_archive']
			|| (string) $object->labels->name !== $state['plural_label']
			|| (string) $object->labels->singular_name !== $state['singular_label'] ) {
			return false;
		}
		$supports = array_keys( get_all_post_type_supports( $slug ) );
		$supports = array_values( array_diff( $supports, array( 'autosave' ) ) );
		$taxonomies = get_object_taxonomies( $slug, 'names' );
		return array() === array_diff( $state['supports'], $supports ) && array() === array_diff( $state['taxonomies'], $taxonomies );
	}
	$object = get_taxonomy( $slug );
	if ( ! $object
		|| (bool) $object->public !== $state['public']
		|| (bool) $object->hierarchical !== $state['hierarchical']
		|| (bool) $object->show_ui !== $state['show_ui']
		|| (bool) $object->show_in_rest !== $state['show_in_rest']
		|| (bool) $object->show_admin_column !== $state['show_admin_column']
		|| (string) $object->labels->name !== $state['plural_label']
		|| (string) $object->labels->singular_name !== $state['singular_label'] ) {
		return false;
	}
	$object_types = array_values( (array) $object->object_type );
	return array() === array_diff( $state['object_types'], $object_types );
}

/** @return array<string,mixed>|WP_Error */
function wpagent_cptui_state( string $kind, array $definition, array $definitions, ?int $content_count = null ) {
	$slug = (string) ( $definition['name'] ?? '' );
	if ( null === $content_count ) {
		$content_count = wpagent_cptui_content_count( $kind, $slug );
		if ( is_wp_error( $content_count ) ) {
			return $content_count;
		}
	}
	$managed = wpagent_cptui_managed_keys( $kind );
	$unmanaged = array_values( array_diff( array_keys( $definition ), $managed ) );
	sort( $unmanaged, SORT_STRING );
	$state = array(
		'slug' => $slug,
		'singular_label' => wpagent_cptui_display_text( $definition['singular_label'] ?? '' ),
		'plural_label' => wpagent_cptui_display_text( $definition['label'] ?? '' ),
		'description' => wpagent_cptui_display_text( $definition['description'] ?? '' ),
		'public' => wpagent_cptui_bool( $definition['public'] ?? false ),
		'hierarchical' => wpagent_cptui_bool( $definition['hierarchical'] ?? false ),
		'show_ui' => wpagent_cptui_bool( $definition['show_ui'] ?? false ),
		'show_in_rest' => wpagent_cptui_bool( $definition['show_in_rest'] ?? false ),
		'rewrite_slug' => is_string( $definition['rewrite_slug'] ?? null ) ? $definition['rewrite_slug'] : '',
		'content_count' => $content_count,
		'runtime_registered' => 'post_type' === $kind ? post_type_exists( $slug ) : taxonomy_exists( $slug ),
		'registration_conflict' => false,
		'unmanaged_fields' => $unmanaged,
		'option_hash' => wpagent_cptui_option_hash( $definitions ),
	);
	if ( 'post_type' === $kind ) {
		$state['supports'] = is_array( $definition['supports'] ?? null ) ? array_values( array_unique( $definition['supports'] ) ) : array();
		$state['taxonomies'] = is_array( $definition['taxonomies'] ?? null ) ? array_values( array_unique( $definition['taxonomies'] ) ) : array();
		sort( $state['supports'], SORT_STRING );
		sort( $state['taxonomies'], SORT_STRING );
		$state['has_archive'] = wpagent_cptui_bool( $definition['has_archive'] ?? false );
	} else {
		$state['object_types'] = is_array( $definition['object_types'] ?? null ) ? array_values( array_unique( $definition['object_types'] ) ) : array();
		sort( $state['object_types'], SORT_STRING );
		$state['show_admin_column'] = wpagent_cptui_bool( $definition['show_admin_column'] ?? false );
	}
	$state['registration_conflict'] = $state['runtime_registered'] && ! wpagent_cptui_runtime_matches( $kind, $state );
	return $state;
}

/** @return string|WP_Error */
function wpagent_cptui_acquire_lock( string $kind ) {
	$option = 'wp_agent_cptui_lock_' . $kind;
	$token  = wpagent_companion_acquire_option_lock( $option, 300 );
	if ( is_string( $token ) ) {
		return $token;
	}
	return new WP_Error( 'wpagent_cptui_busy', __( 'Die CPT-UI-Definitionen werden gerade geändert. Versuche es gleich erneut.', 'wp-agent-companion' ), array( 'status' => 409 ) );
}

function wpagent_cptui_release_lock( string $kind, string $token ): void {
	wpagent_companion_release_option_lock( 'wp_agent_cptui_lock_' . $kind, $token );
}

function wpagent_cptui_clear_option_cache( string $option ): void {
	wp_cache_delete( $option, 'options' );
	wp_cache_delete( 'alloptions', 'options' );
	wp_cache_delete( 'notoptions', 'options' );
}

/** @return array{exists:bool,value:mixed,raw_value:?string,autoload:?string} */
function wpagent_cptui_option_snapshot( string $option ): array {
	global $wpdb;
	$row = $wpdb->get_row( $wpdb->prepare( "SELECT option_value, autoload FROM {$wpdb->options} WHERE option_name = %s", $option ), ARRAY_A );
	return array(
		'exists' => is_array( $row ),
		'value' => is_array( $row ) ? maybe_unserialize( $row['option_value'] ) : null,
		'raw_value' => is_array( $row ) ? (string) $row['option_value'] : null,
		'autoload' => is_array( $row ) ? (string) $row['autoload'] : null,
	);
}

function wpagent_cptui_restore_option( string $option, array $snapshot ): bool {
	global $wpdb;
	$current = wpagent_cptui_option_snapshot( $option );
	if ( $current === $snapshot ) {
		return true;
	}
	if ( ! $snapshot['exists'] ) {
		if ( ! $current['exists'] ) {
			return true;
		}
		$deleted = $wpdb->delete(
			$wpdb->options,
			array( 'option_name' => $option, 'option_value' => $current['raw_value'], 'autoload' => $current['autoload'] ),
			array( '%s', '%s', '%s' )
		);
		if ( 1 !== $deleted ) {
			return false;
		}
		wpagent_cptui_clear_option_cache( $option );
		return ! wpagent_cptui_option_snapshot( $option )['exists'];
	}
	if ( ! $current['exists'] ) {
		$inserted = $wpdb->insert(
			$wpdb->options,
			array( 'option_name' => $option, 'option_value' => $snapshot['raw_value'], 'autoload' => $snapshot['autoload'] ),
			array( '%s', '%s', '%s' )
		);
		if ( 1 !== $inserted ) {
			return false;
		}
	} else {
		$restored = $wpdb->update(
			$wpdb->options,
			array( 'option_value' => $snapshot['raw_value'], 'autoload' => $snapshot['autoload'] ),
			array( 'option_name' => $option, 'option_value' => $current['raw_value'], 'autoload' => $current['autoload'] ),
			array( '%s', '%s' ),
			array( '%s', '%s', '%s' )
		);
		if ( 1 !== $restored ) {
			return false;
		}
	}
	wpagent_cptui_clear_option_cache( $option );
	return wpagent_cptui_option_snapshot( $option ) === $snapshot;
}

/** @return true|WP_Error */
function wpagent_cptui_persist( string $kind, array $definitions, array $snapshot ) {
	$config = wpagent_cptui_kinds()[ $kind ];
	if ( count( $definitions ) > 500 ) {
		return new WP_Error( 'wpagent_cptui_limit', __( 'CPT UI darf über wpagently höchstens 500 Definitionen je Art speichern.', 'wp-agent-companion' ), array( 'status' => 413, 'write_performed' => false ) );
	}
	$serialized = maybe_serialize( $definitions );
	if ( strlen( $serialized ) > 2 * MB_IN_BYTES ) {
		return new WP_Error( 'wpagent_cptui_size', __( 'Der CPT-UI-Definitionszustand überschreitet die Sicherheitsgrenze von 2 MB.', 'wp-agent-companion' ), array( 'status' => 413, 'write_performed' => false ) );
	}
	$option = $config['option'];
	$write_performed = false;
	if ( ! $snapshot['exists'] ) {
		if ( ! wpagent_companion_add_option_create_only( $option, $definitions, '', false ) ) {
			return new WP_Error( 'wpagent_cptui_conflict', __( 'Die CPT-UI-Definitionen wurden parallel geändert. Lade den aktuellen Zustand neu.', 'wp-agent-companion' ), array( 'status' => 409, 'write_performed' => false ) );
		}
		$write_performed = true;
	} else {
		$old_value = $snapshot['value'];
		$candidate = sanitize_option( $option, $definitions );
		$candidate = apply_filters( "pre_update_option_{$option}", $candidate, $old_value, $option );
		$candidate = apply_filters( 'pre_update_option', $candidate, $option, $old_value );
		if ( $candidate !== $definitions ) {
			return new WP_Error( 'wpagent_cptui_filter', __( 'Ein WordPress-Filter wollte den verifizierten CPT-UI-Zustand verändern. Die Speicherung wurde abgebrochen.', 'wp-agent-companion' ), array( 'status' => 409, 'write_performed' => false ) );
		}
		if ( $snapshot['raw_value'] !== $serialized ) {
			global $wpdb;
			do_action( 'update_option', $option, $old_value, $definitions );
			$updated = $wpdb->query(
				$wpdb->prepare(
					"UPDATE {$wpdb->options} SET option_value = %s WHERE option_name = %s AND option_value = %s",
					$serialized,
					$option,
					$snapshot['raw_value']
				)
			);
			if ( 1 !== $updated ) {
				wpagent_cptui_clear_option_cache( $option );
				return new WP_Error( 'wpagent_cptui_conflict', __( 'Die CPT-UI-Definitionen wurden parallel geändert. Lade den aktuellen Zustand neu.', 'wp-agent-companion' ), array( 'status' => 409, 'write_performed' => false ) );
			}
			$write_performed = true;
			wpagent_cptui_clear_option_cache( $option );
			do_action( "update_option_{$option}", $old_value, $definitions, $option );
			do_action( 'updated_option', $option, $old_value, $definitions );
		}
	}
	$current = get_option( $option, null );
	if ( ! is_array( $current ) || wpagent_cptui_canonicalize( $current ) !== wpagent_cptui_canonicalize( $definitions ) ) {
		if ( ! $write_performed ) {
			return new WP_Error( 'wpagent_cptui_conflict', __( 'Die CPT-UI-Definitionen wurden parallel geändert. Lade den aktuellen Zustand neu.', 'wp-agent-companion' ), array( 'status' => 409, 'write_performed' => false ) );
		}
		return new WP_Error( 'wpagent_cptui_verify', __( 'CPT UI hat den verifizierten Definitionszustand nicht unverändert gespeichert.', 'wp-agent-companion' ), array( 'status' => 500, 'write_performed' => true ) );
	}
	set_transient( 'cptui_flush_rewrite_rules', 'true', 5 * MINUTE_IN_SECONDS );
	return true;
}

/** @return array<string,mixed> */
function wpagent_cptui_output_schema( string $kind ): array {
	$properties = array(
		'slug' => array( 'type' => 'string' ),
		'singular_label' => array( 'type' => 'string' ),
		'plural_label' => array( 'type' => 'string' ),
		'description' => array( 'type' => 'string' ),
		'public' => array( 'type' => 'boolean' ),
		'hierarchical' => array( 'type' => 'boolean' ),
		'show_ui' => array( 'type' => 'boolean' ),
		'show_in_rest' => array( 'type' => 'boolean' ),
		'rewrite_slug' => array( 'type' => 'string' ),
		'content_count' => array( 'type' => 'integer' ),
		'runtime_registered' => array( 'type' => 'boolean' ),
		'registration_conflict' => array( 'type' => 'boolean' ),
		'unmanaged_fields' => array( 'type' => 'array', 'items' => array( 'type' => 'string' ) ),
		'option_hash' => array( 'type' => 'string', 'pattern' => '^[a-f0-9]{64}$' ),
	);
	if ( 'post_type' === $kind ) {
		$properties['supports'] = array( 'type' => 'array', 'items' => array( 'type' => 'string' ) );
		$properties['taxonomies'] = array( 'type' => 'array', 'items' => array( 'type' => 'string' ) );
		$properties['has_archive'] = array( 'type' => 'boolean' );
	} else {
		$properties['object_types'] = array( 'type' => 'array', 'items' => array( 'type' => 'string' ) );
		$properties['show_admin_column'] = array( 'type' => 'boolean' );
	}
	return array( 'type' => 'object', 'properties' => $properties, 'required' => array_keys( $properties ), 'additionalProperties' => false );
}
