<?php
/** Bounded writers for OceanWP breadcrumbs and Ocean Extra post formats. */

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

function wpagent_ability_update_oceanwp_breadcrumbs_customizer(): array {
	return wpagent_oceanwp_free_surface_writer_ability(
		'breadcrumbs',
		__( 'OceanWP-Breadcrumbs aktualisieren', 'wp-agent-companion' ),
		__( 'Aktualisiert die eng begrenzte native OceanWP-Breadcrumb-Sichtbarkeit, Standardquelle und Position mit vollständigem Theme-Mods-Compare-and-Swap, Rücklesen und selektivem Rollback.', 'wp-agent-companion' ),
		'wpagent_exec_update_oceanwp_breadcrumbs_customizer'
	);
}

/** @return array<string,mixed>|WP_Error */
function wpagent_exec_update_oceanwp_breadcrumbs_customizer( $input ) {
	$ready = wpagent_oceanwp_free_surface_require_breadcrumbs();
	if ( is_wp_error( $ready ) ) {
		return $ready;
	}
	return wpagent_oceanwp_free_surface_update( 'breadcrumbs', $input );
}

function wpagent_ability_update_oceanwp_post_format_overrides(): array {
	return array(
		'label'               => __( 'Ocean-Extra-Beitragsformat aktualisieren', 'wp-agent-companion' ),
		'description'         => __( 'Aktualisiert eng begrenzte Link- oder Quote-Formatvorgaben eines einzelnen Ocean-Extra-Beitrags mit Hash, Sperre, Provider-Read-back und fail-closed Recovery. HTML und Shortcodes bleiben ausgeschlossen.', 'wp-agent-companion' ),
		'category'            => 'wp-agent',
		'input_schema'        => array(
			'type'                 => 'object',
			'properties'           => array(
				'post_id'       => array( 'type' => 'integer', 'minimum' => 1 ),
				'expected_hash' => array( 'type' => 'string', 'pattern' => '^[a-f0-9]{64}$' ),
				'settings'      => wpagent_oceanwp_free_surface_post_format_state_schema(),
			),
			'required'             => array( 'post_id', 'expected_hash', 'settings' ),
			'additionalProperties' => false,
		),
		'output_schema'       => array(
			'type'                 => 'object',
			'properties'           => array(
				'post_id'    => array( 'type' => 'integer', 'minimum' => 1 ),
				'post_type'  => array( 'type' => 'string', 'enum' => array( 'post' ) ),
				'format'     => array( 'type' => 'string', 'enum' => array( 'link', 'quote' ) ),
				'overrides'  => wpagent_oceanwp_free_surface_post_format_state_schema( true ),
				'state_hash' => array( 'type' => 'string', 'pattern' => '^[a-f0-9]{64}$' ),
				'changed'    => array( 'type' => 'boolean' ),
				'verified'   => array( 'type' => 'boolean' ),
			),
			'required'             => array( 'post_id', 'post_type', 'format', 'overrides', 'state_hash', 'changed', 'verified' ),
			'additionalProperties' => false,
		),
		'execute_callback'    => 'wpagent_exec_update_oceanwp_post_format_overrides',
		'permission_callback' => 'wpagent_perm_edit_posts',
		// A failed multi-meta provider write may have committed one field. It must never be retried blindly.
		'meta'                => array( 'show_in_rest' => true, 'annotations' => array( 'readonly' => false, 'destructive' => false, 'idempotent' => false ) ),
	);
}

/** @param array<string,mixed> $settings */
function wpagent_oceanwp_free_surface_post_format_settings_are_safe( string $format, array $settings ): bool {
	$catalog = wpagent_oceanwp_free_surface_post_format_catalog( $format );
	if ( ! $settings || array_diff( array_keys( $settings ), array_keys( $catalog ) ) ) {
		return false;
	}
	foreach ( $settings as $field => $override ) {
		if ( ! is_array( $override ) || ! isset( $override['mode'] ) || ! is_string( $override['mode'] ) ) {
			return false;
		}
		if ( 'inherit' === $override['mode'] ) {
			if ( array_key_exists( 'value', $override ) ) {
				return false;
			}
			continue;
		}
		if ( 'set' !== $override['mode'] || ! array_key_exists( 'value', $override ) ) {
			return false;
		}
		$definition = $catalog[ $field ];
		$valid = 'url' === $definition['kind']
			? wpagent_oceanwp_free_surface_post_format_url_is_safe( $override['value'] )
			: ( 'text' === $definition['kind'] ? wpagent_oceanwp_free_surface_post_format_plain_text_is_safe( $override['value'], WPAGENT_OCEANWP_FREE_SURFACE_MAX_POST_FORMAT_BYTES ) : is_string( $override['value'] ) && in_array( $override['value'], $definition['values'], true ) );
		if ( ! $valid ) {
			return false;
		}
	}
	return true;
}

/** @return array<string,mixed>|WP_Error */
function wpagent_oceanwp_free_surface_post_format_settings( string $format, $value ) {
	$schema = wpagent_oceanwp_free_surface_post_format_state_schema();
	if ( ! is_array( $value ) || is_wp_error( rest_validate_value_from_schema( $value, $schema, 'settings' ) ) ) {
		return new WP_Error( 'wpagent_oceanwp_free_surface_post_format_invalid_settings', __( 'Die Ocean-Extra-Beitragsformateinstellungen sind ungültig.', 'wp-agent-companion' ), array( 'status' => 400 ) );
	}
	$settings = rest_sanitize_value_from_schema( $value, $schema, 'settings' );
	if ( is_wp_error( $settings ) || ! is_array( $settings ) || ! wpagent_oceanwp_free_surface_post_format_settings_are_safe( $format, $settings ) ) {
		return new WP_Error( 'wpagent_oceanwp_free_surface_post_format_invalid_settings', __( 'Die Ocean-Extra-Beitragsformateinstellungen sind ungültig.', 'wp-agent-companion' ), array( 'status' => 400 ) );
	}
	return $settings;
}

/** @return WP_Error */
function wpagent_oceanwp_free_surface_post_format_write_conflict() {
	return new WP_Error( 'wpagent_oceanwp_free_surface_post_format_conflict', __( 'Die Ocean-Extra-Beitragsformatvorgaben wurden gleichzeitig geändert. Lies sie neu.', 'wp-agent-companion' ), array( 'status' => 409, 'mutation_started' => false ) );
}

/** @return WP_Error */
function wpagent_oceanwp_free_surface_post_format_write_recovery_required() {
	return new WP_Error( 'wpagent_oceanwp_free_surface_post_format_write_unverified', __( 'Die Ocean-Extra-Beitragsformatmetadaten konnten nach dem Schreiben nicht einzeln verifiziert werden.', 'wp-agent-companion' ), array( 'status' => 500, 'mutation_started' => true ) );
}

/** @return array{exists:bool,id:int,value:string}|WP_Error */
function wpagent_oceanwp_free_surface_post_format_write_field( int $post_id, string $meta_key, array $previous, array $override ) {
	global $wpdb;
	$current = wpagent_oceanwp_free_surface_post_format_meta_row( $post_id, $meta_key );
	if ( is_wp_error( $current ) ) {
		return $current;
	}
	if ( maybe_serialize( $current ) !== maybe_serialize( $previous ) ) {
		return wpagent_oceanwp_free_surface_post_format_write_conflict();
	}
	if ( ! $previous['exists'] ) {
		if ( 'inherit' === $override['mode'] ) {
			return $previous;
		}
		$value = $override['value'];
		$sanitized = sanitize_meta( $meta_key, wp_unslash( $value ), 'post', get_object_subtype( 'post', $post_id ) );
		if ( ! is_string( $sanitized ) || $sanitized !== $value ) {
			return new WP_Error( 'wpagent_oceanwp_free_surface_post_format_precondition', __( 'Ein Metadatenfilter hat die geprüfte Ocean-Extra-Beitragsformatänderung verändert.', 'wp-agent-companion' ), array( 'status' => 409, 'mutation_started' => false ) );
		}
		try {
			$meta_id = add_post_meta( $post_id, $meta_key, $value, true );
		} catch ( Throwable $error ) {
			// add_post_meta() can throw from a post-meta hook after its INSERT. We cannot
			// distinguish that case safely, so require recovery instead of inviting a retry.
			return wpagent_oceanwp_free_surface_post_format_write_recovery_required();
		}
		if ( ! is_int( $meta_id ) || $meta_id < 1 ) {
			return wpagent_oceanwp_free_surface_post_format_write_conflict();
		}
		$current = wpagent_oceanwp_free_surface_post_format_meta_row( $post_id, $meta_key );
		if ( is_wp_error( $current ) || ! $current['exists'] || $current['id'] !== $meta_id || $current['value'] !== $value ) {
			return wpagent_oceanwp_free_surface_post_format_write_recovery_required();
		}
		return $current;
	}

	$value = 'inherit' === $override['mode'] ? '' : $override['value'];
	if ( 'inherit' !== $override['mode'] ) {
		$sanitized = sanitize_meta( $meta_key, wp_unslash( $value ), 'post', get_object_subtype( 'post', $post_id ) );
		if ( ! is_string( $sanitized ) || $sanitized !== $value ) {
			return new WP_Error( 'wpagent_oceanwp_free_surface_post_format_precondition', __( 'Ein Metadatenfilter hat die geprüfte Ocean-Extra-Beitragsformatänderung verändert.', 'wp-agent-companion' ), array( 'status' => 409, 'mutation_started' => false ) );
		}
	}
	try {
		if ( 'inherit' === $override['mode'] ) {
			$check = apply_filters( 'delete_post_metadata', null, $post_id, $meta_key, $previous['value'], false );
			if ( null !== $check ) {
				return wpagent_oceanwp_free_surface_post_format_write_conflict();
			}
			do_action( 'delete_post_meta', array( $previous['id'] ), $post_id, $meta_key, $previous['value'] );
			do_action( 'delete_postmeta', array( $previous['id'] ) );
			$changed = $wpdb->query( $wpdb->prepare(
				"DELETE FROM {$wpdb->postmeta} WHERE meta_id = %d AND post_id = %d AND meta_key = %s AND meta_value = %s",
				$previous['id'],
				$post_id,
				$meta_key,
				$previous['value']
			) );
		} else {
			$check = apply_filters( 'update_post_metadata', null, $post_id, $meta_key, $value, $previous['value'] );
			if ( null !== $check ) {
				return wpagent_oceanwp_free_surface_post_format_write_conflict();
			}
			do_action( 'update_post_meta', $previous['id'], $post_id, $meta_key, $value );
			do_action( 'update_postmeta', $previous['id'], $post_id, $meta_key, $value );
			$changed = $wpdb->query( $wpdb->prepare(
				"UPDATE {$wpdb->postmeta} SET meta_value = %s WHERE meta_id = %d AND post_id = %d AND meta_key = %s AND meta_value = %s",
				$value,
				$previous['id'],
				$post_id,
				$meta_key,
				$previous['value']
			) );
		}
	} catch ( Throwable $error ) {
		return wpagent_oceanwp_free_surface_post_format_write_conflict();
	}
	if ( 1 !== $changed ) {
		return false === $changed
			? new WP_Error( 'wpagent_oceanwp_free_surface_storage', __( 'Die Ocean-Extra-Beitragsformatmetadaten konnten nicht atomar gespeichert werden.', 'wp-agent-companion' ), array( 'status' => 502, 'mutation_started' => false ) )
			: wpagent_oceanwp_free_surface_post_format_write_conflict();
	}
	try {
		wp_cache_delete( $post_id, 'post_meta' );
		if ( 'inherit' === $override['mode'] ) {
			do_action( 'deleted_post_meta', array( $previous['id'] ), $post_id, $meta_key, $previous['value'] );
			do_action( 'deleted_postmeta', array( $previous['id'] ) );
		} else {
			do_action( 'updated_post_meta', $previous['id'], $post_id, $meta_key, $value );
			do_action( 'updated_postmeta', $previous['id'], $post_id, $meta_key, $value );
		}
	} catch ( Throwable $error ) {
		return wpagent_oceanwp_free_surface_post_format_write_recovery_required();
	}
	$current = wpagent_oceanwp_free_surface_post_format_meta_row( $post_id, $meta_key );
	if ( is_wp_error( $current ) ) {
		return wpagent_oceanwp_free_surface_post_format_write_recovery_required();
	}
	$expected = 'inherit' === $override['mode']
		? array( 'exists' => false, 'id' => 0, 'value' => '' )
		: array( 'exists' => true, 'id' => $previous['id'], 'value' => $value );
	return maybe_serialize( $current ) === maybe_serialize( $expected ) ? $current : wpagent_oceanwp_free_surface_post_format_write_recovery_required();
}

/** @return array<string,mixed>|WP_Error */
function wpagent_exec_update_oceanwp_post_format_overrides( $input ) {
	$input  = is_array( $input ) ? $input : array();
	if ( array_diff( array_keys( $input ), array( 'post_id', 'expected_hash', 'settings' ) )
		|| ! isset( $input['post_id'] )
		|| ! is_int( $input['post_id'] )
		|| $input['post_id'] < 1 ) {
		return new WP_Error( 'wpagent_oceanwp_free_surface_post_format_invalid_settings', __( 'Die Ocean-Extra-Beitragsformatanfrage ist ungültig.', 'wp-agent-companion' ), array( 'status' => 400 ) );
	}
	$target = wpagent_oceanwp_free_surface_post_format_target( absint( $input['post_id'] ?? 0 ) );
	if ( is_wp_error( $target ) ) {
		return $target;
	}
	$settings = wpagent_oceanwp_free_surface_post_format_settings( $target['format'], $input['settings'] ?? null );
	if ( is_wp_error( $settings ) ) {
		return $settings;
	}
	$expected_hash = $input['expected_hash'] ?? null;
	if ( ! is_string( $expected_hash ) || ! preg_match( '/^[a-f0-9]{64}$/D', $expected_hash ) ) {
		return new WP_Error( 'wpagent_oceanwp_free_surface_post_format_invalid_settings', __( 'expected_hash muss ein SHA-256-Hash sein.', 'wp-agent-companion' ), array( 'status' => 400 ) );
	}
	$post_id = $target['post']->ID;
	$blog_id = $target['blog_id'];
	$lock    = wpagent_oceanwp_post_acquire_lock( $post_id );
	if ( is_wp_error( $lock ) ) {
		return $lock;
	}
	$write_started = false;
	try {
		$target = wpagent_oceanwp_free_surface_post_format_target( $post_id, $blog_id );
		if ( is_wp_error( $target ) ) {
			return $target;
		}
		$settings = wpagent_oceanwp_free_surface_post_format_settings( $target['format'], $settings );
		if ( is_wp_error( $settings ) ) {
			return $settings;
		}
		$previous = wpagent_oceanwp_free_surface_post_format_snapshot( $target );
		if ( is_wp_error( $previous ) ) {
			return $previous;
		}
		$previous_hash = wpagent_oceanwp_free_surface_post_format_hash( $previous );
		if ( ! hash_equals( $previous_hash, $expected_hash ) ) {
			return new WP_Error( 'wpagent_oceanwp_free_surface_post_format_conflict', __( 'Die Ocean-Extra-Beitragsformatvorgaben haben sich seit dem Lesen geändert. Lies sie neu.', 'wp-agent-companion' ), array( 'status' => 409, 'current_hash' => $previous_hash ) );
		}
		$expected = wpagent_oceanwp_free_surface_post_format_expected_snapshot( $previous, $settings );
		if ( hash_equals( $previous_hash, wpagent_oceanwp_free_surface_post_format_hash( $expected ) ) ) {
			if ( ! wpagent_oceanwp_post_refresh_lock( $post_id, $lock ) || is_wp_error( wpagent_oceanwp_free_surface_post_format_target( $post_id, $blog_id ) ) ) {
				return new WP_Error( 'wpagent_oceanwp_free_surface_post_format_lock_lost', __( 'Die Ocean-Extra-Beitragsformatänderung hat ihre exklusive Sperre verloren.', 'wp-agent-companion' ), array( 'status' => 409 ) );
			}
			return array(
				'post_id'    => $previous['post_id'],
				'post_type'  => $previous['post_type'],
				'format'     => $previous['format'],
				'overrides'  => wpagent_oceanwp_free_surface_post_format_normalize( $previous ),
				'state_hash' => $previous_hash,
				'changed'    => false,
				'verified'   => true,
			);
		}
		foreach ( $settings as $field => $override ) {
			if ( maybe_serialize( $expected['meta'][ $field ] ) === maybe_serialize( $previous['meta'][ $field ] ) ) {
				continue;
			}
			if ( ! wpagent_oceanwp_post_refresh_lock( $post_id, $lock ) ) {
				return $write_started ? wpagent_oceanwp_free_surface_post_format_recovery_required( $post_id, $blog_id ) : new WP_Error( 'wpagent_oceanwp_free_surface_post_format_lock_lost', __( 'Die Ocean-Extra-Beitragsformatänderung hat ihre exklusive Sperre verloren.', 'wp-agent-companion' ), array( 'status' => 409 ) );
			}
			$write_target = wpagent_oceanwp_free_surface_post_format_target( $post_id, $blog_id );
			if ( is_wp_error( $write_target ) ) {
				return $write_started ? wpagent_oceanwp_free_surface_post_format_recovery_required( $post_id, $blog_id ) : $write_target;
			}
			$meta_key = wpagent_oceanwp_free_surface_post_format_catalog( $previous['format'] )[ $field ]['meta'];
			$written = wpagent_oceanwp_free_surface_post_format_write_field( $write_target['post']->ID, $meta_key, $previous['meta'][ $field ], $override );
			if ( is_wp_error( $written ) ) {
				$data = $written->get_error_data();
				return $write_started || ( is_array( $data ) && ! empty( $data['mutation_started'] ) )
					? wpagent_oceanwp_free_surface_post_format_recovery_required( $post_id, $blog_id )
					: $written;
			}
			$write_started                = true;
			$expected['meta'][ $field ] = $written;
			if ( ! wpagent_oceanwp_post_refresh_lock( $post_id, $lock ) || is_wp_error( wpagent_oceanwp_free_surface_post_format_target( $post_id, $blog_id ) ) ) {
				return wpagent_oceanwp_free_surface_post_format_recovery_required( $post_id, $blog_id );
			}
		}
		$fresh_target = wpagent_oceanwp_free_surface_post_format_target( $post_id, $blog_id );
		$fresh        = is_array( $fresh_target ) ? wpagent_oceanwp_free_surface_post_format_snapshot( $fresh_target ) : $fresh_target;
		if ( is_wp_error( $fresh ) || ! hash_equals( wpagent_oceanwp_free_surface_post_format_hash( $expected ), wpagent_oceanwp_free_surface_post_format_hash( $fresh ) ) || ! wpagent_oceanwp_post_refresh_lock( $post_id, $lock ) ) {
			return wpagent_oceanwp_free_surface_post_format_recovery_required( $post_id, $blog_id );
		}
		$runtime = wpagent_oceanwp_free_surface_post_format_runtime_state( $fresh );
		if ( is_wp_error( $runtime ) || ! wpagent_oceanwp_free_surface_post_format_runtime_matches( $fresh['format'], $settings, $runtime ) || ! wpagent_oceanwp_post_refresh_lock( $post_id, $lock ) ) {
			return wpagent_oceanwp_free_surface_post_format_recovery_required( $post_id, $blog_id );
		}
		$verified = wpagent_oceanwp_free_surface_post_format_target( $post_id, $blog_id );
		if ( is_wp_error( $verified ) ) {
			return wpagent_oceanwp_free_surface_post_format_recovery_required( $post_id, $blog_id );
		}
		return array(
			'post_id'    => $fresh['post_id'],
			'post_type'  => $fresh['post_type'],
			'format'     => $fresh['format'],
			'overrides'  => wpagent_oceanwp_free_surface_post_format_normalize( $fresh ),
			'state_hash' => wpagent_oceanwp_free_surface_post_format_hash( $fresh ),
			'changed'    => true,
			'verified'   => true,
		);
	} finally {
		wpagent_oceanwp_post_release_lock( $post_id, $lock );
	}
}
