<?php
/**
 * Ability: wp-agent/update-post
 *
 * Partielles Update eines bestehenden Beitrags oder einer Seite: nur die
 * übergebenen Felder werden geändert. Der post_type ist implizit über post_id
 * bekannt, daher ist kein eigener post_type-Parameter nötig; der Fine-Grained-
 * Check current_user_can( 'edit_post', $post_id ) löst die richtige Capability
 * (edit_posts vs. edit_pages) über WordPress' Meta-Capability-Zuordnung
 * automatisch auf.
 *
 * Nach dem Schreiben liest die Ability jedes geänderte Feld zurück und meldet
 * pro Feld verified:true/false (String-Vergleich), analog zum
 * set-seo-meta-Muster.
 *
 * @package WPAgentCompanion
 */

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

define( 'WPAGENT_UPDATE_POST_LOCK_TTL', 300 );

function wpagent_update_post_lock_name( int $post_id ): string {
	return 'wp_agent_update_post_lock_' . $post_id;
}

/** @return string|WP_Error */
function wpagent_update_post_acquire_lock( int $post_id ) {
	$name  = wpagent_update_post_lock_name( $post_id );
	$token = wpagent_companion_acquire_option_lock( $name, WPAGENT_UPDATE_POST_LOCK_TTL );
	if ( is_string( $token ) ) {
		return $token;
	}
	return new WP_Error( 'wpagent_post_busy', __( 'Dieser Beitrag wird gerade geändert. Versuche es gleich erneut.', 'wp-agent-companion' ), array( 'status' => 409 ) );
}

function wpagent_update_post_release_lock( int $post_id, string $token ): void {
	wpagent_companion_release_option_lock( wpagent_update_post_lock_name( $post_id ), $token );
}

function wpagent_ability_update_post(): array {
	return array(
		'label'               => __( 'Beitrag aktualisieren', 'wp-agent-companion' ),
		'description'         => __( 'Aktualisiert einzelne Felder eines bestehenden Beitrags oder einer Seite konfliktgeschützt mit dem state_hash aus get-post, einem beitragsspezifischen Lock und exakter Rückleseprüfung.', 'wp-agent-companion' ),
		'category'            => 'wp-agent',
		'input_schema'        => array(
			'type'       => 'object',
			'properties' => array(
				'post_id'             => array( 'type' => 'integer' ),
				'expected_state_hash' => array(
					'type'        => 'string',
					'pattern'     => '^[a-f0-9]{64}$',
					'description' => __( 'Exakter state_hash aus get-post. Verhindert das Überschreiben zwischenzeitlicher Änderungen.', 'wp-agent-companion' ),
				),
				'title'            => array( 'type' => 'string' ),
				'content_markdown' => array(
					'type'        => 'string',
					'description' => __( 'Wird wie bei create-post-from-markdown serverseitig zu Gutenberg-Blocks konvertiert.', 'wp-agent-companion' ),
				),
				'excerpt'          => array( 'type' => 'string' ),
				'slug'             => array( 'type' => 'string' ),
				'status'           => array(
					'type' => 'string',
					'enum' => array( 'draft', 'publish', 'pending', 'private' ),
				),
				'date'             => array(
					'type'        => 'string',
					'description' => __( 'Parsebares Datum/Zeit. Angaben ohne Zeitzone werden als Website-Zeit interpretiert.', 'wp-agent-companion' ),
				),
				'menu_order'       => array( 'type' => 'integer' ),
				'parent'           => array( 'type' => 'integer' ),
			),
			'required'   => array( 'post_id', 'expected_state_hash' ),
		),
		'output_schema'       => array(
			'type'       => 'object',
			'properties' => array(
				'post_id'        => array( 'type' => 'integer' ),
				'post_type'      => array( 'type' => 'string' ),
				'written'        => array( 'type' => 'object' ),
				'verified'       => array( 'type' => 'boolean' ),
				'block_count'    => array( 'type' => 'integer' ),
				'freeform_count' => array( 'type' => 'integer' ),
				'block_types'    => array(
					'type'  => 'array',
					'items' => array( 'type' => 'string' ),
				),
				'has_freeform'        => array( 'type' => 'boolean' ),
				'previous_state_hash' => array( 'type' => 'string', 'pattern' => '^[a-f0-9]{64}$' ),
				'state_hash'          => array( 'type' => 'string', 'pattern' => '^[a-f0-9]{64}$' ),
			),
		),
		'execute_callback'    => 'wpagent_exec_update_post',
		'permission_callback' => 'wpagent_perm_edit_posts',
		'meta'                => array(
			'show_in_rest' => true,
			'annotations'  => array(
				'readonly'    => false,
				'destructive' => false,
				'idempotent'  => true,
			),
		),
	);
}

/**
 * @param mixed $input
 * @return array|WP_Error
 */
function wpagent_exec_update_post( $input ) {
	$input   = is_array( $input ) ? $input : array();
	$post_id = (int) ( $input['post_id'] ?? 0 );
	$post    = get_post( $post_id );

	if ( ! $post ) {
		return new WP_Error( 'wpagent_no_post', __( 'Beitrag nicht gefunden.', 'wp-agent-companion' ), array( 'status' => 404 ) );
	}
	if ( ! current_user_can( 'edit_post', $post_id ) ) {
		return new WP_Error( 'wpagent_forbidden', __( 'Keine Berechtigung.', 'wp-agent-companion' ), array( 'status' => 403 ) );
	}
	$expected_state_hash = is_string( $input['expected_state_hash'] ?? null ) ? strtolower( $input['expected_state_hash'] ) : '';
	if ( ! preg_match( '/^[a-f0-9]{64}$/', $expected_state_hash ) ) {
		return new WP_Error( 'wpagent_post_expected_hash', __( 'expected_state_hash muss der vollständige state_hash aus get-post sein.', 'wp-agent-companion' ), array( 'status' => 400 ) );
	}
	$lock = wpagent_update_post_acquire_lock( $post_id );
	if ( is_wp_error( $lock ) ) {
		return $lock;
	}
	try {
		return wpagent_exec_update_post_locked( $input, $post_id, $expected_state_hash );
	} finally {
		wpagent_update_post_release_lock( $post_id, $lock );
	}
}

/** @return array|WP_Error */
function wpagent_exec_update_post_locked( array $input, int $post_id, string $expected_state_hash ) {
	clean_post_cache( $post_id );
	$post = get_post( $post_id );
	if ( ! $post ) {
		return new WP_Error( 'wpagent_no_post', __( 'Beitrag nicht gefunden.', 'wp-agent-companion' ), array( 'status' => 404 ) );
	}
	if ( ! current_user_can( 'edit_post', $post_id ) ) {
		return new WP_Error( 'wpagent_forbidden', __( 'Keine Berechtigung.', 'wp-agent-companion' ), array( 'status' => 403 ) );
	}
	$previous_state_hash = wpagent_post_state_hash( $post );
	if ( ! hash_equals( $previous_state_hash, $expected_state_hash ) ) {
		return new WP_Error(
			'wpagent_post_conflict',
			__( 'Der Beitrag wurde seit dem Lesen verändert. Lies ihn neu und wiederhole den Vorgang mit dem aktuellen state_hash.', 'wp-agent-companion' ),
			array( 'status' => 409, 'current_state_hash' => $previous_state_hash )
		);
	}

	$postarr = array( 'ID' => $post_id );
	$expect  = array(); // Feld => Erwartungswert für die Re-Read-Verifikation.

	if ( array_key_exists( 'title', $input ) ) {
		$postarr['post_title'] = (string) $input['title'];
		$expect['title']       = $postarr['post_title'];
	}

	$content_report = null;
	if ( array_key_exists( 'content_markdown', $input ) ) {
		$detection = wpagent_exec_detect_builder( array( 'post_id' => $post_id ) );
		if ( is_wp_error( $detection ) ) {
			return $detection;
		}
		if ( ! wpagent_builder_content_write_supported( $detection, $post->post_type ) ) {
			return wpagent_builder_content_write_error( $detection );
		}
		$converter                = new WPAgent_Markdown_To_Blocks();
		$content                  = $converter->convert( (string) $input['content_markdown'] );
		$content_report           = wpagent_block_report( $content );
		$validated                = wpagent_validate_generated_core_blocks( $content );
		if ( is_wp_error( $validated ) ) {
			return $validated;
		}
		$postarr['post_content']  = $content;
		$expect['content']        = $content;
	}

	if ( array_key_exists( 'excerpt', $input ) ) {
		$postarr['post_excerpt'] = (string) $input['excerpt'];
		$expect['excerpt']       = $postarr['post_excerpt'];
	}

	if ( array_key_exists( 'slug', $input ) ) {
		$postarr['post_name'] = sanitize_title( (string) $input['slug'] );
		$expect['slug']       = $postarr['post_name'];
	}

	if ( array_key_exists( 'status', $input ) ) {
		$status = (string) $input['status'];
		if ( ! in_array( $status, array( 'draft', 'publish', 'pending', 'private' ), true ) ) {
			return new WP_Error( 'wpagent_bad_status', sprintf( __( 'Ungültiger Status "%s".', 'wp-agent-companion' ), $status ), array( 'status' => 400 ) );
		}
		$status_cap = wpagent_check_post_status_cap( $post->post_type, $status );
		if ( is_wp_error( $status_cap ) ) {
			return $status_cap;
		}
		$postarr['post_status'] = $status;
		$expect['status']       = $status;
		if ( 'publish' === $status && ! array_key_exists( 'date', $input ) && ( 'future' === $post->post_status || wpagent_post_has_future_date( $post ) ) ) {
			$postarr['post_date']     = current_time( 'mysql' );
			$postarr['post_date_gmt'] = current_time( 'mysql', true );
			$postarr['edit_date']     = true;
		}
	}

	if ( array_key_exists( 'date', $input ) ) {
		$parsed_date = wpagent_parse_post_date( (string) $input['date'] );
		if ( is_wp_error( $parsed_date ) ) {
			return $parsed_date;
		}
		// post_date_gmt MUSS hier explizit mitgesetzt werden, sonst gewinnt beim
		// Update das alte post_date_gmt: wp_update_post() macht
		// array_merge( $altePost, $unserPostarr ), und weil wir sonst nur
		// post_date setzen, überlebt das alte post_date_gmt den Merge unverändert
		// und wird von wp_insert_post() als "schon gesetzt" übernommen (unser
		// neues post_date würde für die interne publish/future-Auswertung
		// schlicht ignoriert). Zusätzlich überschreibt WordPress' eigene "Drafts
		// shouldn't be assigned a date"-Logik unser Datum komplett mit "jetzt",
		// wenn der AKTUELLE Status draft/pending/auto-draft ist und post_date_gmt
		// beim Beitrag noch der Nullwert ist — außer edit_date ist gesetzt (das
		// Flag, das auch der Bearbeiten-Bildschirm bei einer manuellen
		// Datumsänderung mitschickt).
		$postarr['post_date']     = $parsed_date['post_date'];
		$postarr['post_date_gmt'] = $parsed_date['post_date_gmt'];
		$postarr['edit_date']     = true;
		$expect['date']           = $postarr['post_date'];
	}

	if ( array_key_exists( 'menu_order', $input ) ) {
		$postarr['menu_order'] = (int) $input['menu_order'];
		$expect['menu_order']  = $postarr['menu_order'];
	}

	if ( array_key_exists( 'parent', $input ) ) {
		$parent = (int) $input['parent'];
		if ( $parent === $post_id ) {
			return new WP_Error( 'wpagent_bad_parent', __( 'Ein Beitrag kann nicht sein eigener Eltern-Beitrag sein.', 'wp-agent-companion' ), array( 'status' => 400 ) );
		}
		if ( $parent > 0 && ! get_post( $parent ) ) {
			return new WP_Error( 'wpagent_bad_parent', __( 'Eltern-Beitrag nicht gefunden.', 'wp-agent-companion' ), array( 'status' => 400 ) );
		}
		$postarr['post_parent'] = $parent;
		$expect['parent']       = $parent;
	}

	if ( array() === $expect ) {
		return new WP_Error( 'wpagent_no_fields', __( 'Kein zu aktualisierendes Feld übergeben.', 'wp-agent-companion' ), array( 'status' => 400 ) );
	}

	$res = wp_update_post( $postarr, true );
	if ( is_wp_error( $res ) ) {
		return $res;
	}

	$fresh = get_post( $post_id );
	if ( ! $fresh ) {
		return new WP_Error( 'wpagent_post_verify_failed', __( 'Der Beitrag konnte nach dem Update nicht erneut gelesen werden. Prüfe den aktuellen Zustand.', 'wp-agent-companion' ), array( 'status' => 500 ) );
	}

	$written  = array();
	$verified = true;
	foreach ( $expect as $field => $value ) {
		$actual = match ( $field ) {
			'title'      => $fresh->post_title,
			'content'    => $fresh->post_content,
			'excerpt'    => $fresh->post_excerpt,
			'slug'       => $fresh->post_name,
			'status'     => $fresh->post_status,
			'date'       => $fresh->post_date,
			'menu_order' => (int) $fresh->menu_order,
			'parent'     => (int) $fresh->post_parent,
			default      => null,
		};
		$ok                = ( (string) $actual === (string) $value );
		$written[ $field ] = $ok;
		if ( ! $ok ) {
			$verified = false;
		}
	}

	$result = array(
		'post_id'   => $post_id,
		'post_type' => $fresh->post_type,
		'written'   => $written,
		'verified'  => $verified,
		'previous_state_hash' => $previous_state_hash,
		'state_hash'          => wpagent_post_state_hash( $fresh ),
	);
	if ( null !== $content_report ) {
		$result['block_count']    = $content_report['block_count'];
		$result['freeform_count'] = $content_report['freeform_count'];
		$result['block_types']    = $content_report['block_types'];
		$result['has_freeform']   = $content_report['freeform_count'] > 0;
	}
	if ( ! $verified ) {
		return new WP_Error(
			'wpagent_post_verify_failed',
				__( 'Mindestens ein geändertes Beitragsfeld bestand die Rückleseprüfung nicht. Der abweichende aktuelle Zustand bleibt erhalten und muss neu gelesen werden.', 'wp-agent-companion' ),
			array( 'status' => 500, 'current_state_hash' => $result['state_hash'], 'written' => $written )
		);
	}

	return $result;
}
