<?php
/** Transactional management for MB Relationships definitions and connections. */

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

/** @return array<string,mixed> */
function wpagent_metabox_relationship_settings_schema(): array {
	return array( 'type' => 'object', 'additionalProperties' => true );
}

/** @return array<string,mixed> */
function wpagent_metabox_relationship_state_schema(): array {
	return array(
		'type' => 'object',
		'properties' => array(
			'id' => array( 'type' => 'integer', 'minimum' => 1 ),
			'title' => array( 'type' => 'string' ),
			'slug' => array( 'type' => 'string' ),
			'status' => array( 'type' => 'string' ),
			'settings' => wpagent_metabox_relationship_settings_schema(),
			'relationship' => wpagent_metabox_relationship_settings_schema(),
			'hash' => array( 'type' => 'string', 'pattern' => '^[a-f0-9]{64}$' ),
		),
		'required' => array( 'id', 'title', 'slug', 'status', 'settings', 'relationship', 'hash' ),
		'additionalProperties' => false,
	);
}

/** @return array<string,mixed> */
function wpagent_metabox_connection_schema(): array {
	return array(
		'type' => 'object',
		'properties' => array(
			'relationship_id' => array( 'type' => 'string' ),
			'from' => array( 'type' => 'integer', 'minimum' => 1 ),
			'to' => array( 'type' => 'integer', 'minimum' => 1 ),
			'order_from' => array( 'type' => 'integer', 'minimum' => 0 ),
			'order_to' => array( 'type' => 'integer', 'minimum' => 0 ),
			'exists' => array( 'type' => 'boolean' ),
			'hash' => array( 'type' => 'string', 'pattern' => '^[a-f0-9]{64}$' ),
		),
		'required' => array( 'relationship_id', 'from', 'to', 'order_from', 'order_to', 'exists', 'hash' ),
		'additionalProperties' => false,
	);
}

/** @return array<string,mixed> */
function wpagent_metabox_relationship_ability( string $label, string $description, array $input, array $output, string $callback, bool $readonly = false, bool $destructive = false ): array {
	return array(
		'label' => $label,
		'description' => $description,
		'category' => 'wp-agent',
		'input_schema' => $input,
		'output_schema' => $output,
		'execute_callback' => $callback,
		'permission_callback' => 'wpagent_perm_manage_options',
		'meta' => array(
			'show_in_rest' => true,
			'annotations' => array( 'readonly' => $readonly, 'destructive' => $destructive, 'idempotent' => $readonly ),
		),
	);
}

/** @return array<string,mixed> */
function wpagent_ability_list_meta_box_relationships(): array {
	return wpagent_metabox_relationship_ability(
		__( 'Meta-Box-Beziehungen auflisten', 'wp-agent-companion' ),
		__( 'Listet persistente MB-Relationships-Definitionen mit Konflikthash.', 'wp-agent-companion' ),
		array( 'type' => 'object', 'properties' => array( 'search' => array( 'type' => 'string', 'maxLength' => 100 ), 'limit' => array( 'type' => 'integer', 'minimum' => 1, 'maximum' => 100, 'default' => 50 ), 'offset' => array( 'type' => 'integer', 'minimum' => 0, 'maximum' => 10000, 'default' => 0 ) ), 'additionalProperties' => false ),
		array( 'type' => 'object', 'properties' => array( 'relationships' => array( 'type' => 'array', 'items' => wpagent_metabox_relationship_state_schema() ), 'total' => array( 'type' => 'integer', 'minimum' => 0 ) ), 'required' => array( 'relationships', 'total' ), 'additionalProperties' => false ),
		'wpagent_metabox_list_relationships',
		true
	);
}

/** @return array<string,mixed> */
function wpagent_ability_get_meta_box_relationship(): array {
	return wpagent_metabox_relationship_ability(
		__( 'Meta-Box-Beziehung lesen', 'wp-agent-companion' ),
		__( 'Liest eine persistente MB-Relationships-Definition mit Konflikthash.', 'wp-agent-companion' ),
		array( 'type' => 'object', 'properties' => array( 'id' => array( 'type' => 'integer', 'minimum' => 1 ) ), 'required' => array( 'id' ), 'additionalProperties' => false ),
		wpagent_metabox_relationship_state_schema(),
		'wpagent_metabox_get_relationship',
		true
	);
}

/** @return array<string,mixed> */
function wpagent_ability_create_meta_box_relationship(): array {
	return wpagent_metabox_relationship_ability(
		__( 'Meta-Box-Beziehung erstellen', 'wp-agent-companion' ),
		__( 'Erstellt eine geprüfte MB-Relationships-Definition und verifiziert beide Providerzustände.', 'wp-agent-companion' ),
		array( 'type' => 'object', 'properties' => array( 'title' => array( 'type' => 'string', 'minLength' => 1, 'maxLength' => 200 ), 'settings' => wpagent_metabox_relationship_settings_schema() ), 'required' => array( 'title', 'settings' ), 'additionalProperties' => false ),
		wpagent_metabox_relationship_state_schema(),
		'wpagent_metabox_create_relationship'
	);
}

/** @return array<string,mixed> */
function wpagent_ability_update_meta_box_relationship(): array {
	return wpagent_metabox_relationship_ability(
		__( 'Meta-Box-Beziehung aktualisieren', 'wp-agent-companion' ),
		__( 'Ersetzt eine unveränderte MB-Relationships-Definition mit Sperre, Read-back und Rollback.', 'wp-agent-companion' ),
		array(
			'type' => 'object',
			'properties' => array(
				'id' => array( 'type' => 'integer', 'minimum' => 1 ),
				'expected_hash' => array( 'type' => 'string', 'pattern' => '^[a-f0-9]{64}$' ),
				'title' => array( 'type' => 'string', 'minLength' => 1, 'maxLength' => 200 ),
				'settings' => wpagent_metabox_relationship_settings_schema(),
			),
			'required' => array( 'id', 'expected_hash', 'settings' ),
			'additionalProperties' => false,
		),
		wpagent_metabox_relationship_state_schema(),
		'wpagent_metabox_update_relationship'
	);
}

/** @return array<string,mixed> */
function wpagent_ability_delete_meta_box_relationship(): array {
	return wpagent_metabox_relationship_ability(
		__( 'Meta-Box-Beziehung löschen', 'wp-agent-companion' ),
		__( 'Verschiebt eine unveränderte Definition in den Papierkorb oder löscht sie nach ID-Bestätigung dauerhaft.', 'wp-agent-companion' ),
		array(
			'type' => 'object',
			'properties' => array(
				'id' => array( 'type' => 'integer', 'minimum' => 1 ),
				'expected_hash' => array( 'type' => 'string', 'pattern' => '^[a-f0-9]{64}$' ),
				'force' => array( 'type' => 'boolean', 'default' => false ),
				'confirm_relationship_id' => array( 'type' => 'string' ),
			),
			'required' => array( 'id', 'expected_hash' ),
			'additionalProperties' => false,
		),
		array( 'type' => 'object', 'properties' => array( 'deleted' => array( 'type' => 'boolean' ), 'previous' => wpagent_metabox_relationship_state_schema() ), 'required' => array( 'deleted', 'previous' ), 'additionalProperties' => false ),
		'wpagent_metabox_delete_relationship',
		false,
		true
	);
}

/** @return array<string,mixed> */
function wpagent_ability_list_meta_box_connections(): array {
	return wpagent_metabox_relationship_ability(
		__( 'Meta-Box-Verbindungen auflisten', 'wp-agent-companion' ),
		__( 'Listet begrenzt die Verbindungen einer freigegebenen Relationship-Seite auf.', 'wp-agent-companion' ),
		array(
			'type' => 'object',
			'properties' => array(
				'relationship_id' => array( 'type' => 'string' ),
				'direction' => array( 'type' => 'string', 'enum' => array( 'from', 'to' ) ),
				'object_id' => array( 'type' => 'integer', 'minimum' => 1 ),
				'limit' => array( 'type' => 'integer', 'minimum' => 1, 'maximum' => 100, 'default' => 50 ),
				'offset' => array( 'type' => 'integer', 'minimum' => 0, 'maximum' => 10000, 'default' => 0 ),
			),
			'required' => array( 'relationship_id', 'direction', 'object_id' ),
			'additionalProperties' => false,
		),
		array( 'type' => 'object', 'properties' => array( 'connections' => array( 'type' => 'array', 'items' => wpagent_metabox_connection_schema() ), 'total' => array( 'type' => 'integer', 'minimum' => 0 ) ), 'required' => array( 'connections', 'total' ), 'additionalProperties' => false ),
		'wpagent_metabox_list_connections',
		true
	);
}

/** @return array<string,mixed> */
function wpagent_ability_add_meta_box_connection(): array {
	$properties = array(
		'relationship_id' => array( 'type' => 'string' ),
		'from' => array( 'type' => 'integer', 'minimum' => 1 ),
		'to' => array( 'type' => 'integer', 'minimum' => 1 ),
		'order_from' => array( 'type' => 'integer', 'minimum' => 0, 'maximum' => 2147483647, 'default' => 1 ),
		'order_to' => array( 'type' => 'integer', 'minimum' => 0, 'maximum' => 2147483647, 'default' => 1 ),
	);
	return wpagent_metabox_relationship_ability(
		__( 'Meta-Box-Verbindung hinzufügen', 'wp-agent-companion' ),
		__( 'Fügt zwei gültige Objekte über die offizielle MB-Relationships-API zusammen und prüft den Datenbankzustand.', 'wp-agent-companion' ),
		array( 'type' => 'object', 'properties' => $properties, 'required' => array( 'relationship_id', 'from', 'to' ), 'additionalProperties' => false ),
		wpagent_metabox_connection_schema(),
		'wpagent_metabox_add_connection'
	);
}

/** @return array<string,mixed> */
function wpagent_ability_delete_meta_box_connection(): array {
	return wpagent_metabox_relationship_ability(
		__( 'Meta-Box-Verbindung löschen', 'wp-agent-companion' ),
		__( 'Löscht eine unveränderte Verbindung über die offizielle MB-Relationships-API mit verifiziertem Rollback.', 'wp-agent-companion' ),
		array(
			'type' => 'object',
			'properties' => array(
				'relationship_id' => array( 'type' => 'string' ),
				'from' => array( 'type' => 'integer', 'minimum' => 1 ),
				'to' => array( 'type' => 'integer', 'minimum' => 1 ),
				'expected_hash' => array( 'type' => 'string', 'pattern' => '^[a-f0-9]{64}$' ),
			),
			'required' => array( 'relationship_id', 'from', 'to', 'expected_hash' ),
			'additionalProperties' => false,
		),
		array( 'type' => 'object', 'properties' => array( 'deleted' => array( 'type' => 'boolean' ), 'previous' => wpagent_metabox_connection_schema() ), 'required' => array( 'deleted', 'previous' ), 'additionalProperties' => false ),
		'wpagent_metabox_delete_connection',
		false,
		true
	);
}

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

function wpagent_metabox_hash( $value ): string {
	return hash( 'sha256', (string) wp_json_encode( wpagent_metabox_canonicalize( $value ), JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE ) );
}

function wpagent_metabox_relationship_post_exists( int $post_id ): bool {
	global $wpdb;
	return (bool) $wpdb->get_var( $wpdb->prepare( "SELECT 1 FROM {$wpdb->posts} WHERE `ID` = %d LIMIT 1", $post_id ) );
}

function wpagent_metabox_relationship_table_available(): bool {
	global $wpdb;
	$table = (string) ( $wpdb->mb_relationships ?? '' );
	return '' !== $table && $table === $wpdb->get_var( $wpdb->prepare( 'SHOW TABLES LIKE %s', $wpdb->esc_like( $table ) ) );
}

/** @return array<string,mixed>|WP_Error */
function wpagent_metabox_relationship_state( int $post_id ) {
	$post = get_post( $post_id );
	if ( ! $post instanceof WP_Post || 'mb-relationship' !== $post->post_type ) {
		return new WP_Error( 'wpagent_metabox_relationship_not_found', __( 'Meta-Box-Beziehung nicht gefunden.', 'wp-agent-companion' ), array( 'status' => 404 ) );
	}
	$settings     = get_post_meta( $post_id, 'settings', true );
	$relationship = get_post_meta( $post_id, 'relationship', true );
	if ( ! is_array( $settings ) || ! is_array( $relationship ) ) {
		return new WP_Error( 'wpagent_metabox_relationship_invalid', __( 'Die gespeicherte Meta-Box-Beziehung ist unvollständig.', 'wp-agent-companion' ), array( 'status' => 409 ) );
	}
	$state = array(
		'id' => $post_id,
		'title' => (string) $post->post_title,
		'slug' => (string) $post->post_name,
		'status' => (string) $post->post_status,
		'settings' => $settings,
		'relationship' => $relationship,
	);
	$state['hash'] = wpagent_metabox_hash( $state );
	return $state;
}

function wpagent_metabox_relationship_lock_name( string $identity ): string {
	return 'wp_agent_metabox_relationship_lock_' . hash( 'sha256', $identity );
}

/** @return string|WP_Error */
function wpagent_metabox_relationship_acquire_lock( string $identity ) {
	$option = wpagent_metabox_relationship_lock_name( $identity );
	$token  = wpagent_companion_acquire_option_lock( $option, 300 );
	if ( is_string( $token ) ) {
		return $token;
	}
	return new WP_Error( 'wpagent_metabox_relationship_busy', __( 'Diese Meta-Box-Beziehung wird gerade geändert. Versuche es gleich erneut.', 'wp-agent-companion' ), array( 'status' => 409 ) );
}

function wpagent_metabox_relationship_release_lock( string $identity, string $token ): void {
	wpagent_companion_release_option_lock( wpagent_metabox_relationship_lock_name( $identity ), $token );
}

/** @return array<string,mixed> */
function wpagent_metabox_relationship_snapshot( WP_Post $post ): array {
	return array(
		'post' => array( 'ID' => $post->ID, 'post_title' => $post->post_title, 'post_name' => $post->post_name, 'post_status' => $post->post_status ),
		'settings' => array( 'exists' => metadata_exists( 'post', $post->ID, 'settings' ), 'value' => get_post_meta( $post->ID, 'settings', true ) ),
		'relationship' => array( 'exists' => metadata_exists( 'post', $post->ID, 'relationship' ), 'value' => get_post_meta( $post->ID, 'relationship', true ) ),
	);
}

function wpagent_metabox_restore_relationship_snapshot( array $snapshot, string $expected_hash ): bool {
	$post = $snapshot['post'] ?? array();
	$id   = absint( $post['ID'] ?? 0 );
	if ( ! $id || is_wp_error( wp_update_post( $post, true ) ) ) {
		return false;
	}
	foreach ( array( 'settings', 'relationship' ) as $key ) {
		$state = $snapshot[ $key ] ?? array();
		if ( ! empty( $state['exists'] ) ) {
			update_post_meta( $id, $key, $state['value'] );
		} else {
			delete_post_meta( $id, $key );
		}
	}
	$current = wpagent_metabox_relationship_state( $id );
	return ! is_wp_error( $current ) && hash_equals( $expected_hash, $current['hash'] );
}

/** @return array<string,mixed>|WP_Error */
function wpagent_metabox_save_relationship( WP_Post $post, string $title, array $normalized, string $status = 'publish' ) {
	$settings     = $normalized['settings'];
	$relationship = $normalized['relationship'];
	$id           = (string) $settings['id'];
	$result = wp_update_post(
		array( 'ID' => $post->ID, 'post_title' => $title, 'post_name' => $id, 'post_status' => $status ),
		true
	);
	if ( is_wp_error( $result ) ) {
		return $result;
	}
	update_post_meta( $post->ID, 'settings', $settings );
	update_post_meta( $post->ID, 'relationship', $relationship );
	$fresh = wpagent_metabox_relationship_state( $post->ID );
	if (
		is_wp_error( $fresh ) ||
		$fresh['title'] !== $title ||
		$fresh['slug'] !== $id ||
		$status !== $fresh['status'] ||
		wpagent_metabox_canonicalize( $fresh['settings'] ) !== wpagent_metabox_canonicalize( $settings ) ||
		wpagent_metabox_canonicalize( $fresh['relationship'] ) !== wpagent_metabox_canonicalize( $relationship )
	) {
		return new WP_Error( 'wpagent_metabox_relationship_readback_failed', __( 'Die Meta-Box-Beziehung weicht nach dem Speichern vom erwarteten Zustand ab.', 'wp-agent-companion' ), array( 'status' => 500 ) );
	}
	return $fresh;
}

/** @return array<string,mixed>|WP_Error */
function wpagent_metabox_list_relationships( array $input ) {
	$limit  = min( 100, max( 1, absint( $input['limit'] ?? 50 ) ) );
	$offset = min( 10000, absint( $input['offset'] ?? 0 ) );
	$args = array( 'post_type' => 'mb-relationship', 'post_status' => array( 'publish', 'draft', 'trash' ), 'posts_per_page' => $limit, 'offset' => $offset, 'orderby' => 'ID', 'order' => 'ASC', 'no_found_rows' => false );
	$search = sanitize_text_field( (string) ( $input['search'] ?? '' ) );
	if ( '' !== $search ) {
		$args['s'] = $search;
	}
	$query  = new WP_Query( $args );
	$states = array();
	foreach ( $query->posts as $post ) {
		$state = wpagent_metabox_relationship_state( $post->ID );
		if ( is_wp_error( $state ) ) {
			return $state;
		}
		$states[] = $state;
	}
	return array( 'relationships' => $states, 'total' => (int) $query->found_posts );
}

/** @return array<string,mixed>|WP_Error */
function wpagent_metabox_get_relationship( array $input ) {
	return wpagent_metabox_relationship_state( absint( $input['id'] ?? 0 ) );
}

/** @return array<string,mixed>|WP_Error */
function wpagent_metabox_create_relationship( array $input ) {
	$title = sanitize_text_field( (string) ( $input['title'] ?? '' ) );
	if ( '' === $title ) {
		return new WP_Error( 'wpagent_metabox_relationship_title', __( 'Die Meta-Box-Beziehung benötigt einen Titel.', 'wp-agent-companion' ), array( 'status' => 400 ) );
	}
	$normalized = wpagent_metabox_validate_relationship_settings( is_array( $input['settings'] ?? null ) ? $input['settings'] : array() );
	if ( is_wp_error( $normalized ) ) {
		return $normalized;
	}
	$relationship_id = (string) $normalized['settings']['id'];
	$identity = 'relationship:' . $relationship_id;
	$lock = wpagent_metabox_relationship_acquire_lock( $identity );
	if ( is_wp_error( $lock ) ) {
		return $lock;
	}
	try {
		$existing = get_page_by_path( $relationship_id, OBJECT, 'mb-relationship' );
		if ( $existing instanceof WP_Post ) {
			return new WP_Error( 'wpagent_metabox_relationship_exists', __( 'Eine Meta-Box-Beziehung mit dieser ID existiert bereits.', 'wp-agent-companion' ), array( 'status' => 409 ) );
		}
		if ( MB_Relationships_API::get_relationship( $relationship_id ) ) {
			return new WP_Error( 'wpagent_metabox_relationship_runtime_collision', __( 'Eine per PHP registrierte Meta-Box-Beziehung verwendet diese ID bereits. Sie wurde nicht überschrieben.', 'wp-agent-companion' ), array( 'status' => 409 ) );
		}
		$post_id = wp_insert_post( array( 'post_type' => 'mb-relationship', 'post_title' => $title, 'post_name' => $relationship_id, 'post_status' => 'publish' ), true );
		if ( is_wp_error( $post_id ) ) {
			return $post_id;
		}
		$created_post = get_post( $post_id );
		$saved = $created_post instanceof WP_Post
			? wpagent_metabox_save_relationship( $created_post, $title, $normalized )
			: new WP_Error( 'wpagent_metabox_relationship_save_failed', __( 'Die neu angelegte Meta-Box-Beziehung ist vor der Verifikation verschwunden.', 'wp-agent-companion' ), array( 'status' => 500 ) );
		if ( is_wp_error( $saved ) ) {
			wp_delete_post( $post_id, true );
			if ( wpagent_metabox_relationship_post_exists( $post_id ) ) {
				return new WP_Error( 'wpagent_metabox_relationship_rollback_failed', __( 'Die unvollständige Meta-Box-Beziehung konnte nicht entfernt werden. Prüfe sie sofort manuell.', 'wp-agent-companion' ), array( 'status' => 500 ) );
			}
			return new WP_Error( 'wpagent_metabox_relationship_save_failed', __( 'Die Meta-Box-Beziehung konnte nicht exakt verifiziert werden und wurde zurückgerollt.', 'wp-agent-companion' ), array( 'status' => 500 ) );
		}
		return $saved;
	} finally {
		wpagent_metabox_relationship_release_lock( $identity, $lock );
	}
}

/** @return array<string,mixed>|WP_Error */
function wpagent_metabox_update_relationship( array $input ) {
	$post_id = absint( $input['id'] ?? 0 );
	$expected_hash = strtolower( (string) ( $input['expected_hash'] ?? '' ) );
	if ( ! preg_match( '/^[a-f0-9]{64}$/', $expected_hash ) ) {
		return new WP_Error( 'wpagent_metabox_relationship_hash', __( 'expected_hash muss ein SHA-256-Hash sein.', 'wp-agent-companion' ), array( 'status' => 400 ) );
	}
	$preflight = wpagent_metabox_relationship_state( $post_id );
	if ( is_wp_error( $preflight ) ) {
		return $preflight;
	}
	$identity = 'relationship:' . (string) $preflight['settings']['id'];
	$lock = wpagent_metabox_relationship_acquire_lock( $identity );
	if ( is_wp_error( $lock ) ) {
		return $lock;
	}
	try {
		$before = wpagent_metabox_relationship_state( $post_id );
		if ( is_wp_error( $before ) ) {
			return $before;
		}
		if ( ! hash_equals( $before['hash'], $expected_hash ) ) {
			return new WP_Error( 'wpagent_metabox_relationship_conflict', __( 'Die Meta-Box-Beziehung wurde seit dem Lesen verändert.', 'wp-agent-companion' ), array( 'status' => 409, 'current_hash' => $before['hash'] ) );
		}
		if ( ! in_array( $before['status'], array( 'publish', 'draft' ), true ) ) {
			return new WP_Error( 'wpagent_metabox_relationship_status', __( 'Eine Beziehung im Papierkorb muss vor dem Ändern wiederhergestellt werden.', 'wp-agent-companion' ), array( 'status' => 409 ) );
		}
		$normalized = wpagent_metabox_validate_relationship_settings( is_array( $input['settings'] ?? null ) ? $input['settings'] : array() );
		if ( is_wp_error( $normalized ) ) {
			return $normalized;
		}
		if ( $before['settings']['id'] !== $normalized['settings']['id'] ) {
			return new WP_Error( 'wpagent_metabox_relationship_id_change', __( 'Die Beziehungs-ID kann nicht geändert werden, weil vorhandene Verbindungen sonst verwaisen könnten.', 'wp-agent-companion' ), array( 'status' => 409 ) );
		}
		$current_post = get_post( $post_id );
		if ( ! $current_post instanceof WP_Post ) {
			return new WP_Error( 'wpagent_metabox_relationship_not_found', __( 'Meta-Box-Beziehung nicht gefunden.', 'wp-agent-companion' ), array( 'status' => 404 ) );
		}
		$title    = array_key_exists( 'title', $input ) ? sanitize_text_field( (string) $input['title'] ) : $before['title'];
		$snapshot = wpagent_metabox_relationship_snapshot( $current_post );
		$saved    = '' === $title ? new WP_Error( 'wpagent_metabox_relationship_title', __( 'Die Meta-Box-Beziehung benötigt einen Titel.', 'wp-agent-companion' ), array( 'status' => 400 ) ) : wpagent_metabox_save_relationship( $current_post, $title, $normalized, $before['status'] );
		if ( ! is_wp_error( $saved ) ) {
			return $saved;
		}
		if ( ! wpagent_metabox_restore_relationship_snapshot( $snapshot, $before['hash'] ) ) {
			return new WP_Error( 'wpagent_metabox_relationship_rollback_failed', __( 'Die Beziehungsänderung konnte nicht verifiziert und der vorherige Zustand nicht vollständig wiederhergestellt werden. Prüfe sie sofort manuell.', 'wp-agent-companion' ), array( 'status' => 500 ) );
		}
		return new WP_Error( 'wpagent_metabox_relationship_save_failed', __( 'Die Beziehungsänderung konnte nicht exakt verifiziert werden und wurde zurückgerollt.', 'wp-agent-companion' ), array( 'status' => 500 ) );
	} finally {
		wpagent_metabox_relationship_release_lock( $identity, $lock );
	}
}

/** @return array<int,array<string,int|string>> */
function wpagent_metabox_relationship_rows( string $relationship_id ): array {
	global $wpdb;
	$rows = $wpdb->get_results( $wpdb->prepare( "SELECT `ID`, `from`, `to`, `type`, `order_from`, `order_to` FROM {$wpdb->mb_relationships} WHERE `type` = %s ORDER BY `ID` ASC", $relationship_id ), ARRAY_A );
	return is_array( $rows ) ? $rows : array();
}

/** @param array<int,array<string,int|string>> $rows */
function wpagent_metabox_restore_relationship_rows( string $relationship_id, array $rows ): bool {
	global $wpdb;
	$wpdb->delete( $wpdb->mb_relationships, array( 'type' => $relationship_id ), array( '%s' ) );
	foreach ( $rows as $row ) {
		$inserted = $wpdb->insert(
			$wpdb->mb_relationships,
			array( 'ID' => (int) $row['ID'], 'from' => (int) $row['from'], 'to' => (int) $row['to'], 'type' => $relationship_id, 'order_from' => (int) $row['order_from'], 'order_to' => (int) $row['order_to'] ),
			array( '%d', '%d', '%d', '%s', '%d', '%d' )
		);
		if ( 1 !== $inserted ) {
			return false;
		}
	}
	return wpagent_metabox_canonicalize( wpagent_metabox_relationship_rows( $relationship_id ) ) === wpagent_metabox_canonicalize( $rows );
}

/** @return array<string,mixed>|WP_Error */
function wpagent_metabox_delete_relationship( array $input ) {
	$post_id = absint( $input['id'] ?? 0 );
	$expected_hash = strtolower( (string) ( $input['expected_hash'] ?? '' ) );
	$preflight = wpagent_metabox_relationship_state( $post_id );
	if ( is_wp_error( $preflight ) ) {
		return $preflight;
	}
	$identity = 'relationship:' . (string) $preflight['settings']['id'];
	$lock = wpagent_metabox_relationship_acquire_lock( $identity );
	if ( is_wp_error( $lock ) ) {
		return $lock;
	}
	try {
		$before = wpagent_metabox_relationship_state( $post_id );
		if ( is_wp_error( $before ) ) {
			return $before;
		}
		if ( ! preg_match( '/^[a-f0-9]{64}$/', $expected_hash ) ) {
			return new WP_Error( 'wpagent_metabox_relationship_hash', __( 'expected_hash muss ein SHA-256-Hash sein.', 'wp-agent-companion' ), array( 'status' => 400 ) );
		}
		if ( ! hash_equals( $before['hash'], $expected_hash ) ) {
			return new WP_Error( 'wpagent_metabox_relationship_conflict', __( 'Die Meta-Box-Beziehung wurde seit dem Lesen verändert.', 'wp-agent-companion' ), array( 'status' => 409, 'current_hash' => $before['hash'] ) );
		}
		$force = ! empty( $input['force'] );
		$relationship_id = (string) $before['settings']['id'];
		if ( ! $force && 'trash' === $before['status'] ) {
			return new WP_Error( 'wpagent_metabox_relationship_already_trashed', __( 'Die Beziehung liegt bereits im Papierkorb. Dauerhaftes Löschen verlangt force=true und die exakte ID-Bestätigung.', 'wp-agent-companion' ), array( 'status' => 409 ) );
		}
		if ( $force && ! hash_equals( $relationship_id, (string) ( $input['confirm_relationship_id'] ?? '' ) ) ) {
			return new WP_Error( 'wpagent_metabox_relationship_confirmation', __( 'Für das dauerhafte Löschen muss confirm_relationship_id exakt der Beziehungs-ID entsprechen.', 'wp-agent-companion' ), array( 'status' => 400 ) );
		}
		$connection_rows = array();
		if ( $force && ! empty( $before['settings']['delete_data'] ) ) {
			if ( ! wpagent_metabox_relationship_table_available() ) {
				return new WP_Error( 'wpagent_metabox_relationship_table_missing', __( 'Die MB-Relationships-Datenbanktabelle fehlt. Die Definition wurde nicht gelöscht.', 'wp-agent-companion' ), array( 'status' => 409 ) );
			}
			global $wpdb;
			$connection_rows = wpagent_metabox_relationship_rows( $relationship_id );
			$cleared = $wpdb->delete( $wpdb->mb_relationships, array( 'type' => $relationship_id ), array( '%s' ) );
			if ( false === $cleared || wpagent_metabox_relationship_rows( $relationship_id ) ) {
				$restored = wpagent_metabox_restore_relationship_rows( $relationship_id, $connection_rows );
				return new WP_Error( $restored ? 'wpagent_metabox_relationship_delete_failed' : 'wpagent_metabox_relationship_rollback_failed', $restored ? __( 'Die Verbindungsdaten konnten nicht sicher gelöscht werden. Die Definition blieb unverändert.', 'wp-agent-companion' ) : __( 'Die Verbindungsdaten konnten weder gelöscht noch vollständig wiederhergestellt werden. Prüfe sie sofort manuell.', 'wp-agent-companion' ), array( 'status' => 500 ) );
			}
		}
		// wp_delete_post( $id, false ) only delegates to the trash path for
		// post types with a trash capability. Call wp_trash_post() directly so
		// private provider post types never get deleted permanently by accident.
		$deleted = $force ? wp_delete_post( $post_id, true ) : wp_trash_post( $post_id );
		$delete_failed = $force
			? wpagent_metabox_relationship_post_exists( $post_id )
			: ( ! $deleted || 'trash' !== get_post_status( $post_id ) );
		if ( $delete_failed ) {
			if ( $force && ! empty( $before['settings']['delete_data'] ) && ! wpagent_metabox_restore_relationship_rows( $relationship_id, $connection_rows ) ) {
				return new WP_Error( 'wpagent_metabox_relationship_rollback_failed', __( 'Das Löschen der Definition schlug fehl und ihre Verbindungsdaten konnten nicht vollständig wiederhergestellt werden. Prüfe sie sofort manuell.', 'wp-agent-companion' ), array( 'status' => 500 ) );
			}
			return new WP_Error( 'wpagent_metabox_relationship_delete_failed', __( 'Die Meta-Box-Beziehung konnte nicht vollständig gelöscht werden.', 'wp-agent-companion' ), array( 'status' => 500 ) );
		}
		if ( $force && ! empty( $before['settings']['delete_data'] ) ) {
			global $wpdb;
			$count = (int) $wpdb->get_var( $wpdb->prepare( "SELECT COUNT(*) FROM {$wpdb->mb_relationships} WHERE `type` = %s", $relationship_id ) );
			if ( 0 !== $count ) {
				return new WP_Error( 'wpagent_metabox_relationship_delete_failed', __( 'Die Definition wurde gelöscht, aber ihre Verbindungsdaten sind noch vorhanden. Prüfe die Datenbank sofort manuell.', 'wp-agent-companion' ), array( 'status' => 500 ) );
			}
		}
		return array( 'deleted' => true, 'previous' => $before );
	} finally {
		wpagent_metabox_relationship_release_lock( $identity, $lock );
	}
}

/** @return array<string,mixed>|WP_Error */
function wpagent_metabox_relationship_by_slug( string $relationship_id ) {
	if ( ! preg_match( '/^[a-z][a-z0-9_-]{0,43}$/', $relationship_id ) ) {
		return new WP_Error( 'wpagent_metabox_relationship_id_invalid', __( 'Die Beziehungs-ID ist ungültig.', 'wp-agent-companion' ), array( 'status' => 400 ) );
	}
	$post = get_page_by_path( $relationship_id, OBJECT, 'mb-relationship' );
	if ( ! $post instanceof WP_Post || 'publish' !== $post->post_status ) {
		return new WP_Error( 'wpagent_metabox_relationship_not_found', __( 'Aktive Meta-Box-Beziehung nicht gefunden.', 'wp-agent-companion' ), array( 'status' => 404 ) );
	}
	if ( ! wpagent_metabox_relationship_table_available() ) {
		return new WP_Error( 'wpagent_metabox_relationship_table_missing', __( 'Die MB-Relationships-Datenbanktabelle fehlt.', 'wp-agent-companion' ), array( 'status' => 409 ) );
	}
	$state = wpagent_metabox_relationship_state( $post->ID );
	if ( is_wp_error( $state ) ) {
		return $state;
	}
	$runtime_relationship = MB_Relationships_API::get_relationship( $relationship_id );
	$runtime_settings     = MB_Relationships_API::get_relationship_settings( $relationship_id );
	if ( ! $runtime_relationship || ! is_array( $runtime_settings ) ) {
		return new WP_Error( 'wpagent_metabox_relationship_not_registered', __( 'Meta Box hat diese veröffentlichte Beziehung nicht registriert.', 'wp-agent-companion' ), array( 'status' => 409 ) );
	}
	$normalized = wpagent_metabox_validate_relationship_settings( $state['settings'] );
	if (
		is_wp_error( $normalized ) ||
		wpagent_metabox_canonicalize( $normalized['relationship'] ) !== wpagent_metabox_canonicalize( $state['relationship'] ) ||
		wpagent_metabox_relationship_runtime_signature( $runtime_settings ) !== wpagent_metabox_relationship_runtime_signature( $normalized['settings'] )
	) {
		return new WP_Error( 'wpagent_metabox_relationship_provider_mismatch', __( 'Die gespeicherte Meta-Box-Beziehung stimmt nicht mit der in diesem Request registrierten Providerdefinition überein.', 'wp-agent-companion' ), array( 'status' => 409 ) );
	}
	return $state;
}

/** @return true|WP_Error */
function wpagent_metabox_validate_relationship_object( array $side, int $object_id ) {
	$type = (string) ( $side['object_type'] ?? 'post' );
	if ( 'post' === $type ) {
		$post = get_post( $object_id );
		$allowed = $side['field']['post_type'] ?? $side['post_type'] ?? '';
		$allowed = is_array( $allowed ) ? $allowed : array( $allowed );
		return $post instanceof WP_Post && in_array( $post->post_type, $allowed, true ) ? true : new WP_Error( 'wpagent_metabox_relationship_object_not_found', __( 'Ein Beitragsobjekt passt nicht zur Beziehungsdefinition.', 'wp-agent-companion' ), array( 'status' => 404 ) );
	}
	if ( 'term' === $type ) {
		$term = get_term( $object_id );
		$taxonomy = (string) ( $side['field']['taxonomy'] ?? $side['taxonomy'] ?? '' );
		return $term instanceof WP_Term && $term->taxonomy === $taxonomy ? true : new WP_Error( 'wpagent_metabox_relationship_object_not_found', __( 'Ein Begriffsobjekt passt nicht zur Beziehungsdefinition.', 'wp-agent-companion' ), array( 'status' => 404 ) );
	}
	return get_user_by( 'id', $object_id ) instanceof WP_User ? true : new WP_Error( 'wpagent_metabox_relationship_object_not_found', __( 'Ein Benutzerobjekt passt nicht zur Beziehungsdefinition.', 'wp-agent-companion' ), array( 'status' => 404 ) );
}

/** @return array<string,mixed>|null */
function wpagent_metabox_connection_state( string $relationship_id, int $from, int $to ): ?array {
	global $wpdb;
	$row = $wpdb->get_row( $wpdb->prepare( "SELECT `from`, `to`, `order_from`, `order_to` FROM {$wpdb->mb_relationships} WHERE `type` = %s AND `from` = %d AND `to` = %d LIMIT 1", $relationship_id, $from, $to ), ARRAY_A );
	if ( ! is_array( $row ) ) {
		return null;
	}
	$state = array( 'relationship_id' => $relationship_id, 'from' => (int) $row['from'], 'to' => (int) $row['to'], 'order_from' => (int) $row['order_from'], 'order_to' => (int) $row['order_to'], 'exists' => true );
	$state['hash'] = wpagent_metabox_hash( $state );
	return $state;
}

/** @return array<string,mixed>|WP_Error */
function wpagent_metabox_list_connections( array $input ) {
	$relationship_id = (string) ( $input['relationship_id'] ?? '' );
	$definition = wpagent_metabox_relationship_by_slug( $relationship_id );
	if ( is_wp_error( $definition ) ) {
		return $definition;
	}
	$direction = (string) ( $input['direction'] ?? '' );
	if ( ! in_array( $direction, array( 'from', 'to' ), true ) ) {
		return new WP_Error( 'wpagent_metabox_relationship_direction', __( 'direction muss from oder to sein.', 'wp-agent-companion' ), array( 'status' => 400 ) );
	}
	$object_id = absint( $input['object_id'] ?? 0 );
	$valid = wpagent_metabox_validate_relationship_object( $definition['relationship'][ $direction ], $object_id );
	if ( is_wp_error( $valid ) ) {
		return $valid;
	}
	$limit  = min( 100, max( 1, absint( $input['limit'] ?? 50 ) ) );
	$offset = min( 10000, absint( $input['offset'] ?? 0 ) );
	global $wpdb;
	$total = (int) $wpdb->get_var( $wpdb->prepare( "SELECT COUNT(*) FROM {$wpdb->mb_relationships} WHERE `type` = %s AND `{$direction}` = %d", $relationship_id, $object_id ) );
	$rows = $wpdb->get_results( $wpdb->prepare( "SELECT `from`, `to`, `order_from`, `order_to` FROM {$wpdb->mb_relationships} WHERE `type` = %s AND `{$direction}` = %d ORDER BY `ID` ASC LIMIT %d OFFSET %d", $relationship_id, $object_id, $limit, $offset ), ARRAY_A );
	$connections = array();
	foreach ( $rows as $row ) {
		$state = array( 'relationship_id' => $relationship_id, 'from' => (int) $row['from'], 'to' => (int) $row['to'], 'order_from' => (int) $row['order_from'], 'order_to' => (int) $row['order_to'], 'exists' => true );
		$state['hash'] = wpagent_metabox_hash( $state );
		$connections[] = $state;
	}
	return array( 'connections' => $connections, 'total' => $total );
}

/** @return array<string,mixed>|WP_Error */
function wpagent_metabox_add_connection( array $input ) {
	$relationship_id = (string) ( $input['relationship_id'] ?? '' );
	$preflight = wpagent_metabox_relationship_by_slug( $relationship_id );
	if ( is_wp_error( $preflight ) ) {
		return $preflight;
	}
	$from = absint( $input['from'] ?? 0 );
	$to   = absint( $input['to'] ?? 0 );
	$identity = 'relationship:' . $relationship_id;
	$lock = wpagent_metabox_relationship_acquire_lock( $identity );
	if ( is_wp_error( $lock ) ) {
		return $lock;
	}
	try {
		$definition = wpagent_metabox_relationship_by_slug( $relationship_id );
		if ( is_wp_error( $definition ) ) {
			return $definition;
		}
		foreach ( array( 'from' => $from, 'to' => $to ) as $side => $object_id ) {
			$valid = wpagent_metabox_validate_relationship_object( $definition['relationship'][ $side ], $object_id );
			if ( is_wp_error( $valid ) ) {
				return $valid;
			}
		}
		if ( wpagent_metabox_connection_state( $relationship_id, $from, $to ) ) {
			return new WP_Error( 'wpagent_metabox_connection_exists', __( 'Diese Meta-Box-Verbindung existiert bereits.', 'wp-agent-companion' ), array( 'status' => 409 ) );
		}
		global $wpdb;
		$from_has_one = ! empty( $definition['relationship']['from']['has_one_relationship'] );
		$to_has_one   = ! empty( $definition['relationship']['to']['has_one_relationship'] );
		$from_count   = $from_has_one ? (int) $wpdb->get_var( $wpdb->prepare( "SELECT COUNT(*) FROM {$wpdb->mb_relationships} WHERE `type` = %s AND `from` = %d", $relationship_id, $from ) ) : 0;
		$to_count     = $to_has_one ? (int) $wpdb->get_var( $wpdb->prepare( "SELECT COUNT(*) FROM {$wpdb->mb_relationships} WHERE `type` = %s AND `to` = %d", $relationship_id, $to ) ) : 0;
		if ( $from_count > 0 || $to_count > 0 ) {
			return new WP_Error( 'wpagent_metabox_connection_limit', __( 'Die Beziehungsdefinition erlaubt auf dieser Seite nur eine Verbindung.', 'wp-agent-companion' ), array( 'status' => 409 ) );
		}
		$order_from = min( 2147483647, max( 0, (int) ( $input['order_from'] ?? 1 ) ) );
		$order_to   = min( 2147483647, max( 0, (int) ( $input['order_to'] ?? 1 ) ) );
		$added = MB_Relationships_API::add( $from, $to, $relationship_id, $order_from, $order_to );
		$fresh = wpagent_metabox_connection_state( $relationship_id, $from, $to );
		if ( $added && $fresh && $fresh['order_from'] === $order_from && $fresh['order_to'] === $order_to ) {
			return $fresh;
		}
		MB_Relationships_API::delete( $from, $to, $relationship_id );
		if ( wpagent_metabox_connection_state( $relationship_id, $from, $to ) ) {
			return new WP_Error( 'wpagent_metabox_connection_rollback_failed', __( 'Die fehlerhafte Meta-Box-Verbindung konnte nicht zurückgerollt werden. Prüfe sie sofort manuell.', 'wp-agent-companion' ), array( 'status' => 500 ) );
		}
		return new WP_Error( 'wpagent_metabox_connection_save_failed', __( 'Die Meta-Box-Verbindung konnte nicht exakt verifiziert werden und wurde zurückgerollt.', 'wp-agent-companion' ), array( 'status' => 500 ) );
	} finally {
		wpagent_metabox_relationship_release_lock( $identity, $lock );
	}
}

/** @return array<string,mixed>|WP_Error */
function wpagent_metabox_delete_connection( array $input ) {
	$relationship_id = (string) ( $input['relationship_id'] ?? '' );
	$preflight = wpagent_metabox_relationship_by_slug( $relationship_id );
	if ( is_wp_error( $preflight ) ) {
		return $preflight;
	}
	$from = absint( $input['from'] ?? 0 );
	$to   = absint( $input['to'] ?? 0 );
	$expected_hash = strtolower( (string) ( $input['expected_hash'] ?? '' ) );
	$identity = 'relationship:' . $relationship_id;
	$lock = wpagent_metabox_relationship_acquire_lock( $identity );
	if ( is_wp_error( $lock ) ) {
		return $lock;
	}
	try {
		$definition = wpagent_metabox_relationship_by_slug( $relationship_id );
		if ( is_wp_error( $definition ) ) {
			return $definition;
		}
		$before = wpagent_metabox_connection_state( $relationship_id, $from, $to );
		if ( ! $before ) {
			return new WP_Error( 'wpagent_metabox_connection_not_found', __( 'Meta-Box-Verbindung nicht gefunden.', 'wp-agent-companion' ), array( 'status' => 404 ) );
		}
		if ( ! preg_match( '/^[a-f0-9]{64}$/', $expected_hash ) || ! hash_equals( $before['hash'], $expected_hash ) ) {
			return new WP_Error( 'wpagent_metabox_connection_conflict', __( 'Die Meta-Box-Verbindung wurde seit dem Lesen verändert.', 'wp-agent-companion' ), array( 'status' => 409, 'current_hash' => $before['hash'] ) );
		}
		$deleted = MB_Relationships_API::delete( $from, $to, $relationship_id );
		if ( $deleted && ! wpagent_metabox_connection_state( $relationship_id, $from, $to ) ) {
			return array( 'deleted' => true, 'previous' => $before );
		}
		MB_Relationships_API::add( $from, $to, $relationship_id, $before['order_from'], $before['order_to'] );
		$restored = wpagent_metabox_connection_state( $relationship_id, $from, $to );
		if ( ! $restored || ! hash_equals( $before['hash'], $restored['hash'] ) ) {
			return new WP_Error( 'wpagent_metabox_connection_rollback_failed', __( 'Die Meta-Box-Verbindung konnte nicht gelöscht und ihr vorheriger Zustand nicht wiederhergestellt werden. Prüfe sie sofort manuell.', 'wp-agent-companion' ), array( 'status' => 500 ) );
		}
		return new WP_Error( 'wpagent_metabox_connection_delete_failed', __( 'Die Meta-Box-Verbindung konnte nicht gelöscht werden. Der vorherige Zustand wurde wiederhergestellt.', 'wp-agent-companion' ), array( 'status' => 500 ) );
	} finally {
		wpagent_metabox_relationship_release_lock( $identity, $lock );
	}
}
