<?php
/** Shared state, scoped locks and conditional recovery for taxonomy writes. */

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

define( 'WPAGENT_TERM_WRITE_LOCK_TTL', 60 );

/** @return WP_Term|WP_Error */
function wpagent_term_snapshot( int $term_id, string $taxonomy ) {
	clean_term_cache( $term_id, $taxonomy );
	$term = get_term( $term_id, $taxonomy );
	if ( ! $term instanceof WP_Term ) {
		return new WP_Error( 'wpagent_no_term', __( 'Term nicht gefunden.', 'wp-agent-companion' ), array( 'status' => 404 ) );
	}
	return $term;
}

/** @return array<string,int|string> */
function wpagent_term_state_payload( WP_Term $term ): array {
	return array(
		'term_id'          => (int) $term->term_id,
		'term_taxonomy_id' => (int) $term->term_taxonomy_id,
		'taxonomy'         => (string) $term->taxonomy,
		'name'             => (string) $term->name,
		'slug'             => (string) $term->slug,
		'description'      => (string) $term->description,
		'parent'           => (int) $term->parent,
		'count'            => (int) $term->count,
		'term_group'       => (int) $term->term_group,
	);
}

function wpagent_term_state_hash( WP_Term $term ): string {
	return hash( 'sha256', "wpagently-term-state-v1\0" . serialize( wpagent_term_state_payload( $term ) ) );
}

/** @return string|WP_Error */
function wpagent_term_expected_hash( array $input ) {
	$hash = is_string( $input['expected_state_hash'] ?? null ) ? strtolower( $input['expected_state_hash'] ) : '';
	if ( preg_match( '/^[a-f0-9]{64}$/', $hash ) ) {
		return $hash;
	}
	return new WP_Error( 'wpagent_term_expected_hash', __( 'expected_state_hash muss der vollständige state_hash aus get-term oder list-terms sein.', 'wp-agent-companion' ), array( 'status' => 400 ) );
}

function wpagent_term_lock_option( string $taxonomy, int $term_id ): string {
	return 'wp_agent_term_lock_' . substr( hash( 'sha256', $taxonomy ), 0, 24 ) . '_' . $term_id;
}

function wpagent_post_terms_lock_option( int $post_id, string $taxonomy ): string {
	return 'wp_agent_post_terms_lock_' . $post_id . '_' . substr( hash( 'sha256', $taxonomy ), 0, 24 );
}

/** @return string|WP_Error */
function wpagent_term_acquire_lock( string $taxonomy, int $term_id ) {
	$token = wpagent_companion_acquire_option_lock( wpagent_term_lock_option( $taxonomy, $term_id ), WPAGENT_TERM_WRITE_LOCK_TTL );
	if ( is_string( $token ) ) {
		return $token;
	}
	return new WP_Error( 'wpagent_term_locked', __( 'Dieser Term wird gerade geändert.', 'wp-agent-companion' ), array( 'status' => 409 ) );
}

function wpagent_term_release_lock( string $taxonomy, int $term_id, string $token ): void {
	wpagent_companion_release_option_lock( wpagent_term_lock_option( $taxonomy, $term_id ), $token );
}

function wpagent_term_refresh_lock( string $taxonomy, int $term_id, string $token ): bool {
	return wpagent_companion_refresh_option_lock( wpagent_term_lock_option( $taxonomy, $term_id ), $token, WPAGENT_TERM_WRITE_LOCK_TTL );
}

function wpagent_term_lock_is_owned( string $taxonomy, int $term_id, string $token ): bool {
	return wpagent_companion_option_lock_is_owned( wpagent_term_lock_option( $taxonomy, $term_id ), $token );
}

/** @return string|WP_Error */
function wpagent_post_terms_acquire_lock( int $post_id, string $taxonomy ) {
	$token = wpagent_companion_acquire_option_lock( wpagent_post_terms_lock_option( $post_id, $taxonomy ), WPAGENT_TERM_WRITE_LOCK_TTL );
	if ( is_string( $token ) ) {
		return $token;
	}
	return new WP_Error( 'wpagent_post_terms_locked', __( 'Diese Beitragszuordnung wird gerade geändert.', 'wp-agent-companion' ), array( 'status' => 409 ) );
}

function wpagent_post_terms_release_lock( int $post_id, string $taxonomy, string $token ): void {
	wpagent_companion_release_option_lock( wpagent_post_terms_lock_option( $post_id, $taxonomy ), $token );
}

function wpagent_post_terms_refresh_lock( int $post_id, string $taxonomy, string $token ): bool {
	return wpagent_companion_refresh_option_lock( wpagent_post_terms_lock_option( $post_id, $taxonomy ), $token, WPAGENT_TERM_WRITE_LOCK_TTL );
}

function wpagent_post_terms_lock_is_owned( int $post_id, string $taxonomy, string $token ): bool {
	return wpagent_companion_option_lock_is_owned( wpagent_post_terms_lock_option( $post_id, $taxonomy ), $token );
}

/** @return array{post_id:int,taxonomy:string,term_ids:list<int>}|WP_Error */
function wpagent_post_term_assignment_snapshot( int $post_id, string $taxonomy ) {
	$post = get_post( $post_id );
	if ( ! $post instanceof WP_Post ) {
		return new WP_Error( 'wpagent_no_post', __( 'Beitrag nicht gefunden.', 'wp-agent-companion' ), array( 'status' => 404 ) );
	}
	// The second argument is the object type, not the taxonomy. Clearing with
	// the taxonomy would leave WordPress' post-term relationship cache stale
	// and could turn a concurrent assignment into a false CAS match.
	clean_object_term_cache( $post_id, (string) $post->post_type );
	$ids = wp_get_object_terms( $post_id, $taxonomy, array( 'fields' => 'ids' ) );
	if ( is_wp_error( $ids ) ) {
		return $ids;
	}
	$term_ids = array_values( array_unique( array_map( 'intval', (array) $ids ) ) );
	sort( $term_ids, SORT_NUMERIC );
	return array( 'post_id' => $post_id, 'taxonomy' => $taxonomy, 'term_ids' => $term_ids );
}

/** @param array{post_id:int,taxonomy:string,term_ids:list<int>} $snapshot */
function wpagent_post_terms_state_hash_from_snapshot( array $snapshot ): string {
	return hash( 'sha256', "wpagently-post-terms-state-v1\0" . serialize( $snapshot ) );
}

/** @return string|WP_Error */
function wpagent_post_term_assignment_state_hash( int $post_id, string $taxonomy ) {
	$snapshot = wpagent_post_term_assignment_snapshot( $post_id, $taxonomy );
	return is_wp_error( $snapshot ) ? $snapshot : wpagent_post_terms_state_hash_from_snapshot( $snapshot );
}

/** @return string|WP_Error */
function wpagent_post_terms_expected_hash( array $input ) {
	$hash = is_string( $input['expected_state_hash'] ?? null ) ? strtolower( $input['expected_state_hash'] ) : '';
	if ( preg_match( '/^[a-f0-9]{64}$/', $hash ) ) {
		return $hash;
	}
	return new WP_Error( 'wpagent_post_terms_expected_hash', __( 'expected_state_hash muss der vollständige state_hash aus get-post mit term_taxonomy sein.', 'wp-agent-companion' ), array( 'status' => 400 ) );
}

/** @return WP_Error */
function wpagent_term_conflict( string $current_hash ) {
	return new WP_Error(
		'wpagent_term_conflict',
		__( 'Der Term 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' => $current_hash )
	);
}

/** @return WP_Error */
function wpagent_post_terms_conflict( string $current_hash ) {
	return new WP_Error(
		'wpagent_post_terms_conflict',
		__( 'Die Term-Zuordnung wurde seit dem Lesen verändert. Lies den Beitrag mit term_taxonomy neu und wiederhole den Vorgang.', 'wp-agent-companion' ),
		array( 'status' => 409, 'current_state_hash' => $current_hash )
	);
}

/** @return WP_Error */
function wpagent_term_recovery_required( string $message, array $extra = array() ) {
	return new WP_Error(
		'wpagent_term_recovery_required',
		$message,
		array_merge( array( 'status' => 500, 'recovery_required' => true, 'rollback_verified' => false ), $extra )
	);
}
