<?php
/** Conflict-protected, validated and rollback-capable ACF value writes. */

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

function wpagent_ability_update_acf_field(): array {
	return array(
		'label' => __( 'ACF-Feld aktualisieren', 'wp-agent-companion' ),
		'description' => __( 'Aktualisiert genau ein zugeordnetes ACF-Feld eines Beitrags, Benutzers, Begriffs, Kommentars oder Optionsspeichers anhand seines Field-Keys und Zustands-Hashes. Der Wert wird geprüft, rückgelesen und bei Abweichungen verifiziert zurückgerollt.', 'wp-agent-companion' ),
		'category' => 'wp-agent',
		'input_schema' => array(
			'type' => 'object',
			'properties' => array(
				'post_id' => array( 'type' => 'integer', 'minimum' => 1 ),
				'object_type' => array( 'type' => 'string', 'enum' => array( 'post', 'user', 'term', 'comment', 'options' ) ),
				'object_id' => array( 'type' => array( 'integer', 'string' ) ),
				'field_key' => array( 'type' => 'string', 'pattern' => '^field_[A-Za-z0-9_-]{1,120}$' ),
				'expected_hash' => array( 'type' => 'string', 'pattern' => '^[a-f0-9]{64}$' ),
				'value' => array( 'type' => WPAGENT_MIXED_JSON_TYPES ),
			),
			'required' => array( 'field_key', 'expected_hash', 'value' ),
			'additionalProperties' => false,
		),
		'output_schema' => array(
			'type' => 'object',
			'properties' => array(
				'post_id' => array( 'type' => 'integer' ),
				'object_type' => array( 'type' => 'string', 'enum' => array( 'post', 'user', 'term', 'comment', 'options' ) ),
				'object_id' => array( 'type' => array( 'integer', 'string' ) ),
				'acf_object_id' => array( 'type' => 'string' ),
				'field_key' => array( 'type' => 'string' ),
				'value' => array( 'type' => WPAGENT_MIXED_JSON_TYPES ),
				'state_hash' => array( 'type' => 'string', 'pattern' => '^[a-f0-9]{64}$' ),
				'verified' => array( 'type' => 'boolean' ),
			),
			'required' => array( 'object_type', 'object_id', 'acf_object_id', 'field_key', 'value', 'state_hash', 'verified' ),
			'additionalProperties' => false,
		),
		'execute_callback' => 'wpagent_exec_update_acf_field',
		'permission_callback' => 'wpagent_perm_acf_values',
		'meta' => array( 'show_in_rest' => true, 'annotations' => array( 'readonly' => false, 'destructive' => false, 'idempotent' => false ) ),
	);
}

/** @return array<string,mixed>|WP_Error */
function wpagent_exec_update_acf_field( $input ) {
	$input     = is_array( $input ) ? $input : array();
	$field_key = is_string( $input['field_key'] ?? null ) ? $input['field_key'] : '';
	$hash      = is_string( $input['expected_hash'] ?? null ) ? strtolower( $input['expected_hash'] ) : '';
	$context   = wpagent_acf_value_context_from_input( $input );
	if ( is_wp_error( $context ) ) return $context;
	if ( ! wpagent_acf_value_can_edit_context( $context ) ) return new WP_Error( 'wpagent_forbidden', __( 'Keine Berechtigung.', 'wp-agent-companion' ), array( 'status' => 403 ) );
	if ( ! function_exists( 'update_field' ) || ! function_exists( 'delete_field' ) || ! function_exists( 'get_field' ) ) return new WP_Error( 'wpagent_acf_missing', __( 'Advanced Custom Fields ist nicht aktiv.', 'wp-agent-companion' ), array( 'status' => 409 ) );
	if ( ! preg_match( '/^[a-f0-9]{64}$/', $hash ) ) return new WP_Error( 'wpagent_acf_value_hash', __( 'expected_hash muss der aktuelle SHA-256-Zustands-Hash aus get-acf-fields sein.', 'wp-agent-companion' ), array( 'status' => 400 ) );
	if ( ! array_key_exists( 'value', $input ) ) return new WP_Error( 'wpagent_missing_value', __( 'value fehlt.', 'wp-agent-companion' ), array( 'status' => 400 ) );

	$field = wpagent_acf_value_assigned_field( $context, $field_key );
	if ( is_wp_error( $field ) ) return $field;
	$nodes = 0;
	$value = wpagent_acf_value_validate( $field, $input['value'], 0, $nodes, $context );
	if ( is_wp_error( $value ) ) return $value;
	$lock = wpagent_acf_value_acquire_lock( $context['acf_object_id'], $field_key );
	if ( is_wp_error( $lock ) ) return $lock;

	try {
		$fresh_field = wpagent_acf_value_assigned_field( $context, $field_key );
		if ( is_wp_error( $fresh_field ) ) return $fresh_field;
		$snapshot = wpagent_acf_value_state( $context, $fresh_field );
		if ( ! hash_equals( $snapshot['state_hash'], $hash ) ) {
			return new WP_Error( 'wpagent_acf_value_conflict', __( 'Der ACF-Feldwert wurde seit dem Lesen verändert.', 'wp-agent-companion' ), array( 'status' => 409, 'current_hash' => $snapshot['state_hash'] ) );
		}
		$snapshot_json  = wp_json_encode( $snapshot['raw'] );
		$snapshot_nodes = 0;
		if ( false === $snapshot_json || strlen( $snapshot_json ) > WPAGENT_ACF_VALUE_MAX_BYTES || ! wpagent_acf_value_within_limits( $snapshot['raw'], 0, $snapshot_nodes ) || ! wpagent_acf_value_side_effect_state_is_safe( $snapshot['side_effects'] ) || ! wpagent_acf_value_is_complete( $fresh_field, $snapshot['raw'] ) ) {
			return new WP_Error( 'wpagent_acf_value_unsafe_state', __( 'Der bestehende ACF-Feldwert ist zu groß, zu komplex oder enthält eine unbekannte verschachtelte Struktur.', 'wp-agent-companion' ), array( 'status' => 409 ) );
		}
		$snapshot_export = wpagent_acf_value_export( $fresh_field, $snapshot['raw'] );
		if ( $snapshot_export['redacted'] > 0 ) {
			return new WP_Error( 'wpagent_acf_value_redacted_state', __( 'Der bestehende ACF-Feldwert enthält Referenzen ohne Leserecht und kann mit diesem Konto nicht verlustfrei ersetzt werden.', 'wp-agent-companion' ), array( 'status' => 409 ) );
		}
		$fresh_nodes = 0;
		$value       = wpagent_acf_value_validate( $fresh_field, $input['value'], 0, $fresh_nodes, $context );
		if ( is_wp_error( $value ) ) return $value;
		try {
			update_field( $field_key, $value, $context['acf_object_id'] );
		} catch ( Throwable $error ) {
			return wpagent_acf_value_fail_after_rollback( $context, $fresh_field, $snapshot );
		}
		$fresh = wpagent_acf_value_state( $context, $fresh_field );
		if ( ! wpagent_acf_values_match( $value, $fresh['raw'], $fresh_field ) || ! wpagent_acf_value_side_effects_match( $context, $fresh_field, $value ) ) {
			return wpagent_acf_value_fail_after_rollback( $context, $fresh_field, $snapshot );
		}
		$export = wpagent_acf_value_export( $fresh_field, $fresh['raw'] );
		if ( $export['redacted'] > 0 ) {
			return wpagent_acf_value_fail_after_rollback( $context, $fresh_field, $snapshot );
		}
		$result = array( 'object_type' => $context['object_type'], 'object_id' => $context['object_id'], 'acf_object_id' => (string) $context['acf_object_id'], 'field_key' => $field_key, 'value' => $export['value'], 'state_hash' => $fresh['state_hash'], 'verified' => true );
		if ( 'post' === $context['object_type'] ) $result['post_id'] = $context['post_id'];
		return $result;
	} finally {
		wpagent_acf_value_release_lock( $context['acf_object_id'], $field_key, $lock );
	}
}

/** @param array{exists:bool,raw:mixed,side_effects:array<string,mixed>,state_hash:string} $snapshot @return true|WP_Error */
function wpagent_acf_value_rollback( array $context, array $field, array $snapshot ) {
	try {
		if ( $snapshot['exists'] ) update_field( (string) $field['key'], $snapshot['raw'], $context['acf_object_id'] );
		else delete_field( (string) $field['key'], $context['acf_object_id'] );
		wpagent_acf_value_restore_side_effects( $context, $snapshot['side_effects'] );
	} catch ( Throwable $error ) {
		// ACF and third-party hooks can throw after persistence. Verify below.
	}
	$fresh = wpagent_acf_value_state( $context, $field );
	if ( ! wpagent_acf_value_side_effect_state_is_safe( $fresh['side_effects'] ) || ! hash_equals( $snapshot['state_hash'], $fresh['state_hash'] ) ) {
		return new WP_Error( 'wpagent_acf_value_rollback_unverified', __( 'Der vorherige ACF-Feldwert konnte nicht verifiziert wiederhergestellt werden. Prüfe das Zielobjekt sofort manuell.', 'wp-agent-companion' ), array( 'status' => 500, 'rollback_verified' => false ) );
	}
	return true;
}

/** @param array{exists:bool,raw:mixed,side_effects:array<string,mixed>,state_hash:string} $snapshot @return WP_Error */
function wpagent_acf_value_fail_after_rollback( array $context, array $field, array $snapshot ): WP_Error {
	$rollback = wpagent_acf_value_rollback( $context, $field, $snapshot );
	if ( is_wp_error( $rollback ) ) return $rollback;
	return new WP_Error( 'wpagent_acf_value_unverified', __( 'ACF hat den neuen Feldwert nicht exakt bestätigt. Der vorherige Zustand wurde verifiziert wiederhergestellt.', 'wp-agent-companion' ), array( 'status' => 502, 'rollback_verified' => true ) );
}

/** Compare ACF raw values while accounting for key normalization in compound fields. */
function wpagent_acf_values_match( $expected, $actual, ?array $field = null ): bool {
	if ( is_array( $expected ) || is_array( $actual ) ) {
		if ( is_array( $field ) ) $actual = wpagent_acf_normalize_actual_shape_for_compare( $expected, $actual, $field );
		return wp_json_encode( wpagent_acf_normalize_for_compare( $actual ) ) === wp_json_encode( wpagent_acf_normalize_for_compare( $expected ) );
	}
	return (string) $actual === (string) $expected;
}

/** @return mixed */
function wpagent_acf_normalize_actual_shape_for_compare( $expected, $actual, array $field ) {
	if ( ! is_array( $expected ) || ! is_array( $actual ) ) return $actual;
	$type = (string) ( $field['type'] ?? '' );
	if ( 'group' === $type ) return wpagent_acf_normalize_compound_row_for_compare( $expected, $actual, $field['sub_fields'] ?? array() );
	if ( 'repeater' === $type && array_is_list( $expected ) && array_is_list( $actual ) && count( $expected ) === count( $actual ) ) {
		$result = array(); foreach ( $expected as $index => $row ) $result[] = wpagent_acf_normalize_compound_row_for_compare( $row, $actual[ $index ], $field['sub_fields'] ?? array() ); return $result;
	}
	if ( 'flexible_content' === $type && array_is_list( $expected ) && array_is_list( $actual ) && count( $expected ) === count( $actual ) ) {
		$result = array();
		foreach ( $expected as $index => $row ) {
			$name = is_array( $row ) ? (string) ( $row['acf_fc_layout'] ?? '' ) : '';
			$layout = wpagent_acf_value_layout( $field, $name );
			$result[] = $layout ? wpagent_acf_normalize_compound_row_for_compare( $row, $actual[ $index ], $layout['sub_fields'] ?? array() ) : $actual[ $index ];
		}
		return $result;
	}
	return $actual;
}

/** @return mixed */
function wpagent_acf_normalize_compound_row_for_compare( $expected, $actual, $sub_fields ) {
	if ( ! is_array( $expected ) || ! is_array( $actual ) ) return $actual;
	$result = array();
	foreach ( $expected as $name => $value ) {
		if ( 'acf_fc_layout' === $name ) { $result[ $name ] = $actual[ $name ] ?? null; continue; }
		$child = null;
		foreach ( is_array( $sub_fields ) ? $sub_fields : array() as $candidate ) if ( is_array( $candidate ) && ( $name === (string) ( $candidate['name'] ?? '' ) || $name === (string) ( $candidate['key'] ?? '' ) ) ) { $child = $candidate; break; }
		if ( ! $child ) continue;
		$actual_key = array_key_exists( (string) $child['key'], $actual ) ? (string) $child['key'] : (string) $child['name'];
		if ( array_key_exists( $actual_key, $actual ) ) $result[ (string) $child['name'] ] = wpagent_acf_normalize_actual_shape_for_compare( $value, $actual[ $actual_key ], $child );
	}
	return $result;
}

/** @return mixed */
function wpagent_acf_normalize_for_compare( $value ) {
	if ( is_object( $value ) ) $value = get_object_vars( $value );
	if ( is_array( $value ) ) {
		$result = array(); foreach ( $value as $key => $item ) $result[ $key ] = wpagent_acf_normalize_for_compare( $item );
		if ( ! array_is_list( $result ) ) ksort( $result, SORT_STRING );
		return $result;
	}
	return is_scalar( $value ) || null === $value ? (string) $value : $value;
}
