<?php
/** Sitzungs- und Befehlswarteschlange für den bestätigten Live-Editor. */

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

define( 'WPAGENT_COMPANION_LIVE_EDITOR_SESSION_PREFIX', 'wp_agent_live_editor_session_' );
define( 'WPAGENT_COMPANION_LIVE_EDITOR_USER_PREFIX', 'wp_agent_live_editor_user_' );
define( 'WPAGENT_COMPANION_LIVE_EDITOR_COMMAND_PREFIX', 'wp_agent_live_editor_command_' );
define( 'WPAGENT_COMPANION_LIVE_EDITOR_LOCK_PREFIX', 'wp_agent_live_editor_lock_' );
define( 'WPAGENT_COMPANION_LIVE_EDITOR_SESSION_TTL', 1800 );
define( 'WPAGENT_COMPANION_LIVE_EDITOR_COMMAND_TTL', 300 );
define( 'WPAGENT_COMPANION_LIVE_EDITOR_MAX_PENDING', 20 );
define( 'WPAGENT_COMPANION_LIVE_EDITOR_MAX_RESULTS', 100 );
define( 'WPAGENT_COMPANION_LIVE_EDITOR_MAX_RESULT_BYTES', 100000 );
define( 'WPAGENT_COMPANION_LIVE_EDITOR_MAX_ERROR_BYTES', 10000 );
define( 'WPAGENT_COMPANION_LIVE_EDITOR_MAX_ACTIVE_BYTES', 10000000 );
define( 'WPAGENT_COMPANION_LIVE_EDITOR_MAX_ACTIVE_BLOCKS', 5000 );
define( 'WPAGENT_COMPANION_LIVE_EDITOR_MAX_ACTIVE_DEPTH', 100 );

require_once __DIR__ . '/live-editor-validation.php';

function wpagent_companion_live_editor_session_option( string $session_id ): string {
	return WPAGENT_COMPANION_LIVE_EDITOR_SESSION_PREFIX . $session_id;
}

function wpagent_companion_live_editor_user_option( int $user_id ): string {
	return WPAGENT_COMPANION_LIVE_EDITOR_USER_PREFIX . $user_id;
}

function wpagent_companion_live_editor_command_option( string $command_id ): string {
	return WPAGENT_COMPANION_LIVE_EDITOR_COMMAND_PREFIX . $command_id;
}

/** Löscht einen Optionswert nur dann, wenn er seit dem Lesen unverändert blieb. */
function wpagent_companion_live_editor_delete_record( string $option, $record ): bool {
	global $wpdb;
	$deleted = $wpdb->query(
		$wpdb->prepare(
			"DELETE FROM {$wpdb->options} WHERE option_name = %s AND option_value = %s",
			$option,
			maybe_serialize( $record )
		)
	);
	if ( 1 === $deleted ) {
		wp_cache_delete( $option, 'options' );
		return true;
	}
	return false;
}

/** Speichert einen Optionswert und prüft den tatsächlich persistierten Zustand. */
function wpagent_companion_live_editor_store_record( string $option, $record ): bool {
	update_option( $option, $record, false );
	return get_option( $option, null ) === $record;
}

/** Ersetzt einen Optionswert nur, wenn er seit dem Lesen unverändert blieb. */
function wpagent_companion_live_editor_compare_and_store_record( string $option, $expected, $replacement ): bool {
	global $wpdb;
	$updated = $wpdb->query(
		$wpdb->prepare(
			"UPDATE {$wpdb->options} SET option_value = %s WHERE option_name = %s AND option_value = %s",
			maybe_serialize( $replacement ),
			$option,
			maybe_serialize( $expected )
		)
	);
	wp_cache_delete( $option, 'options' );
	return 1 === $updated && get_option( $option, null ) === $replacement;
}


/** @return string|WP_Error */
function wpagent_companion_live_editor_acquire_lock( string $session_id ) {
	$option = WPAGENT_COMPANION_LIVE_EDITOR_LOCK_PREFIX . $session_id;
	$token  = wpagent_companion_acquire_option_lock( $option, 30, 'expires_at' );
	if ( is_string( $token ) ) {
		return $token;
	}
	return new WP_Error( 'wpagent_live_editor_busy', __( 'Der Live-Editor verarbeitet bereits eine andere Anfrage.', 'wp-agent-companion' ), array( 'status' => 409 ) );
}

function wpagent_companion_live_editor_release_lock( string $session_id, string $token ): void {
	$option = WPAGENT_COMPANION_LIVE_EDITOR_LOCK_PREFIX . $session_id;
	wpagent_companion_release_option_lock( $option, $token, 'expires_at' );
}

function wpagent_companion_live_editor_delete_session_record( array $session ): void {
	$session_id = (string) ( $session['session_id'] ?? '' );
	if ( preg_match( '/^[a-f0-9]{32}$/D', $session_id ) ) {
		$command_ids = array_merge(
			is_array( $session['command_ids'] ?? null ) ? $session['command_ids'] : array(),
			is_array( $session['result_ids'] ?? null ) ? $session['result_ids'] : array()
		);
		foreach ( array_unique( $command_ids ) as $command_id ) {
			if ( is_string( $command_id ) && preg_match( '/^[a-f0-9]{24}$/D', $command_id ) ) {
				$option = wpagent_companion_live_editor_command_option( $command_id );
				$record = get_option( $option, null );
				if ( is_array( $record ) ) {
					wpagent_companion_live_editor_delete_record( $option, $record );
				}
			}
		}
		wpagent_companion_live_editor_delete_record( wpagent_companion_live_editor_session_option( $session_id ), $session );
	}
	$executor_id = absint( $session['executor_id'] ?? 0 );
	if ( $executor_id > 0 ) {
		$index_option = wpagent_companion_live_editor_user_option( $executor_id );
		$indexed      = get_option( $index_option, null );
		if ( is_string( $indexed ) && hash_equals( $session_id, $indexed ) ) {
			wpagent_companion_live_editor_delete_record( $index_option, $indexed );
		}
	}
}

function wpagent_companion_live_editor_delete_user_session( int $user_id ): void {
	$index = get_option( wpagent_companion_live_editor_user_option( $user_id ), null );
	if ( is_string( $index ) && preg_match( '/^[a-f0-9]{32}$/D', $index ) ) {
		$session = get_option( wpagent_companion_live_editor_session_option( $index ), null );
		if ( is_array( $session ) ) {
			wpagent_companion_live_editor_delete_session_record( $session );
			return;
		}
	}
	if ( null !== $index ) {
		wpagent_companion_live_editor_delete_record( wpagent_companion_live_editor_user_option( $user_id ), $index );
	}
}

/** @return array<string,mixed>|WP_Error */
function wpagent_companion_live_editor_get_session( string $session_id ) {
	if ( ! preg_match( '/^[a-f0-9]{32}$/D', $session_id ) ) {
		return new WP_Error( 'wpagent_live_editor_session_expired', __( 'Die Live-Editor-Sitzung ist nicht mehr aktiv.', 'wp-agent-companion' ), array( 'status' => 410 ) );
	}
	$session = get_option( wpagent_companion_live_editor_session_option( $session_id ), null );
	if ( ! is_array( $session ) || ! hash_equals( $session_id, (string) ( $session['session_id'] ?? '' ) ) ) {
		return new WP_Error( 'wpagent_live_editor_session_expired', __( 'Die Live-Editor-Sitzung ist nicht mehr aktiv.', 'wp-agent-companion' ), array( 'status' => 410 ) );
	}
	$executor_id = absint( $session['executor_id'] ?? 0 );
	$index       = get_option( wpagent_companion_live_editor_user_option( $executor_id ), null );
	if ( absint( $session['expires_at'] ?? 0 ) <= time() || ! is_string( $index ) || ! hash_equals( $session_id, $index ) ) {
		wpagent_companion_live_editor_delete_session_record( $session );
		return new WP_Error( 'wpagent_live_editor_session_expired', __( 'Die Live-Editor-Sitzung ist nicht mehr aktiv.', 'wp-agent-companion' ), array( 'status' => 410 ) );
	}
	return $session;
}

/** @return array<string,mixed>|WP_Error */
function wpagent_companion_live_editor_create_session( int $executor_id ) {
	if ( ! current_user_can( 'manage_options' ) ) {
		return new WP_Error( 'wpagent_live_editor_admin_required', __( 'Nur Administratoren dürfen einen Live-Editor öffnen.', 'wp-agent-companion' ), array( 'status' => 403 ) );
	}
	$executor = get_user_by( 'id', $executor_id );
	if ( ! $executor instanceof WP_User || ! wpagent_companion_is_safe_connection_user( $executor ) ) {
		return new WP_Error( 'wpagent_live_editor_unsafe_executor', __( 'Wähle einen freigegebenen Nicht-Administrator mit Inhaltsrechten.', 'wp-agent-companion' ), array( 'status' => 400 ) );
	}
	$executor_lock_id = 'executor-' . $executor_id;
	$executor_lock    = wpagent_companion_live_editor_acquire_lock( $executor_lock_id );
	if ( is_wp_error( $executor_lock ) ) {
		return $executor_lock;
	}
	try {
		$executor = get_user_by( 'id', $executor_id );
		if ( ! $executor instanceof WP_User || ! wpagent_companion_is_safe_connection_user( $executor ) ) {
			return new WP_Error( 'wpagent_live_editor_unsafe_executor', __( 'Wähle einen freigegebenen Nicht-Administrator mit Inhaltsrechten.', 'wp-agent-companion' ), array( 'status' => 400 ) );
		}

		$old_session_id = get_option( wpagent_companion_live_editor_user_option( $executor_id ), null );
		if ( is_string( $old_session_id ) && preg_match( '/^[a-f0-9]{32}$/D', $old_session_id ) ) {
			$old_session_lock = wpagent_companion_live_editor_acquire_lock( $old_session_id );
			if ( is_wp_error( $old_session_lock ) ) {
				return $old_session_lock;
			}
			try {
				wpagent_companion_live_editor_delete_user_session( $executor_id );
			} finally {
				wpagent_companion_live_editor_release_lock( $old_session_id, $old_session_lock );
			}
		} else {
			wpagent_companion_live_editor_delete_user_session( $executor_id );
		}

		try {
			$session_id = bin2hex( random_bytes( 16 ) );
		} catch ( Throwable $error ) {
			return new WP_Error( 'wpagent_live_editor_entropy', __( 'Die sichere Live-Editor-Sitzung konnte nicht erzeugt werden.', 'wp-agent-companion' ), array( 'status' => 500 ) );
		}
		$now     = time();
		$session = array(
			'session_id'   => $session_id,
			'owner_id'     => get_current_user_id(),
			'executor_id'  => $executor_id,
			'status'       => 'active',
			'created_at'   => $now,
			'expires_at'   => $now + WPAGENT_COMPANION_LIVE_EDITOR_SESSION_TTL,
			'last_seen_at' => $now,
			'command_ids'  => array(),
			'result_ids'   => array(),
			'page'         => array(),
		);
		$session_option = wpagent_companion_live_editor_session_option( $session_id );
		$index_option   = wpagent_companion_live_editor_user_option( $executor_id );
		if ( ! wpagent_companion_add_option_create_only( $session_option, $session, '', false ) ) {
			return new WP_Error( 'wpagent_live_editor_session_save', __( 'Die Live-Editor-Sitzung konnte nicht gespeichert werden.', 'wp-agent-companion' ), array( 'status' => 500 ) );
		}
		if ( ! wpagent_companion_live_editor_store_record( $index_option, $session_id ) || get_option( $session_option, null ) !== $session ) {
			wpagent_companion_live_editor_delete_session_record( $session );
			return new WP_Error( 'wpagent_live_editor_session_save', __( 'Die Live-Editor-Sitzung konnte nicht verifiziert gespeichert werden.', 'wp-agent-companion' ), array( 'status' => 500 ) );
		}
		return wpagent_companion_live_editor_public_session( $session );
	} finally {
		wpagent_companion_live_editor_release_lock( $executor_lock_id, $executor_lock );
	}
}

/** @return array<string,mixed> */
function wpagent_companion_live_editor_public_session( array $session ): array {
	$pending = is_array( $session['command_ids'] ?? null ) ? count( $session['command_ids'] ) : 0;
	return array(
		'session_id'       => (string) $session['session_id'],
		'status'           => 'active',
		'executor_id'      => absint( $session['executor_id'] ?? 0 ),
		'created_at'       => gmdate( 'c', absint( $session['created_at'] ?? 0 ) ),
		'expires_at'       => gmdate( 'c', absint( $session['expires_at'] ?? 0 ) ),
		'last_seen_at'     => gmdate( 'c', absint( $session['last_seen_at'] ?? 0 ) ),
		'pending_commands' => $pending,
		'page'             => is_array( $session['page'] ?? null ) ? $session['page'] : array(),
	);
}

/** @return array<string,mixed>|WP_Error */
function wpagent_companion_live_editor_agent_session() {
	$user_id = get_current_user_id();
	$index   = get_option( wpagent_companion_live_editor_user_option( $user_id ), null );
	if ( ! is_string( $index ) ) {
		return new WP_Error( 'wpagent_live_editor_no_session', __( 'Öffne zuerst den Live-Editor im WordPress-Backend und wähle diesen Agent-Nutzer.', 'wp-agent-companion' ), array( 'status' => 409 ) );
	}
	$session = wpagent_companion_live_editor_get_session( $index );
	if ( is_wp_error( $session ) ) {
		return $session;
	}
	return (int) $session['executor_id'] === $user_id
		? $session
		: new WP_Error( 'wpagent_live_editor_command_forbidden', __( 'Diese Live-Editor-Sitzung gehört zu einem anderen Agent-Nutzer.', 'wp-agent-companion' ), array( 'status' => 403 ) );
}

/** @return WP_User|WP_Error */
function wpagent_companion_live_editor_executor( array $session ) {
	$executor = get_user_by( 'id', absint( $session['executor_id'] ?? 0 ) );
	if ( ! $executor instanceof WP_User || ! wpagent_companion_is_safe_connection_user( $executor ) ) {
		return new WP_Error( 'wpagent_live_editor_executor_revoked', __( 'Der gebundene Agent-Nutzer ist nicht mehr für den Live-Editor freigegeben.', 'wp-agent-companion' ), array( 'status' => 403 ) );
	}
	return $executor;
}

/** @return true|WP_Error */
function wpagent_companion_live_editor_check_command_access( array $session, string $action, array $args ) {
	if ( function_exists( 'wpagent_companion_live_editor_is_elementor_action' ) && wpagent_companion_live_editor_is_elementor_action( $action ) ) {
		return wpagent_companion_live_editor_check_elementor_command_access( $session, $action, $args );
	}
	$executor = wpagent_companion_live_editor_executor( $session );
	if ( is_wp_error( $executor ) ) {
		return $executor;
	}
	if ( 'open-post' === $action ) {
		$post_id = absint( $args['post_id'] ?? 0 );
		return $post_id > 0 && user_can( $executor, 'edit_post', $post_id )
			? true
			: new WP_Error( 'wpagent_live_editor_post_forbidden', __( 'Der gebundene Agent-Nutzer darf diesen Beitrag nicht bearbeiten.', 'wp-agent-companion' ), array( 'status' => 403 ) );
	}
	$page    = is_array( $session['page'] ?? null ) ? $session['page'] : array();
	$post_id = absint( $page['post_id'] ?? 0 );
	if ( true !== ( $page['ready'] ?? false ) || 'gutenberg' !== (string) ( $page['kind'] ?? '' ) || 0 === $post_id ) {
		return new WP_Error( 'wpagent_live_editor_editor_required', __( 'Öffne zuerst einen unterstützten Gutenberg-Beitrag im Live-Editor.', 'wp-agent-companion' ), array( 'status' => 409 ) );
	}
	return user_can( $executor, 'edit_post', $post_id )
		? true
		: new WP_Error( 'wpagent_live_editor_post_forbidden', __( 'Der gebundene Agent-Nutzer darf den aktuell geöffneten Beitrag nicht bearbeiten.', 'wp-agent-companion' ), array( 'status' => 403 ) );
}

/** Verschiebt einen abgeschlossenen Befehl in die begrenzte Ergebnisliste. */
function wpagent_companion_live_editor_archive_command( array $session, string $command_id ): array {
	$session['command_ids'] = array_values( array_diff( is_array( $session['command_ids'] ?? null ) ? $session['command_ids'] : array(), array( $command_id ) ) );
	$result_ids             = array_values( array_filter( is_array( $session['result_ids'] ?? null ) ? $session['result_ids'] : array(), 'is_string' ) );
	$result_ids             = array_values( array_diff( $result_ids, array( $command_id ) ) );
	$result_ids[]           = $command_id;
	while ( count( $result_ids ) > WPAGENT_COMPANION_LIVE_EDITOR_MAX_RESULTS ) {
		$expired_result_id = array_shift( $result_ids );
		if ( is_string( $expired_result_id ) && preg_match( '/^[a-f0-9]{24}$/D', $expired_result_id ) ) {
			$option = wpagent_companion_live_editor_command_option( $expired_result_id );
			$record = get_option( $option, null );
			if ( is_array( $record ) ) {
				wpagent_companion_live_editor_delete_record( $option, $record );
			}
		}
	}
	$session['result_ids'] = $result_ids;
	return $session;
}

/** Entfernt tote Queue-Einträge und bewahrt abgelaufene Ergebnisse begrenzt auf. */
function wpagent_companion_live_editor_prune_commands( array $session ) {
	$now = time();
	foreach ( array_values( is_array( $session['command_ids'] ?? null ) ? $session['command_ids'] : array() ) as $command_id ) {
		if ( ! is_string( $command_id ) || ! preg_match( '/^[a-f0-9]{24}$/D', $command_id ) ) {
			$session['command_ids'] = array_values( array_diff( $session['command_ids'], array( $command_id ) ) );
			continue;
		}
		$option  = wpagent_companion_live_editor_command_option( $command_id );
		$command = get_option( $option, null );
		if ( ! is_array( $command ) || ! hash_equals( (string) $session['session_id'], (string) ( $command['session_id'] ?? '' ) ) ) {
			$session['command_ids'] = array_values( array_diff( $session['command_ids'], array( $command_id ) ) );
			continue;
		}
		$status = (string) ( $command['status'] ?? '' );
		if ( in_array( $status, array( 'completed', 'failed', 'denied', 'recovery_required' ), true ) ) {
			$session = wpagent_companion_live_editor_archive_command( $session, $command_id );
			continue;
		}
		if ( absint( $command['expires_at'] ?? 0 ) <= $now ) {
			$command['status']       = 'failed';
			$command['error']        = __( 'Der Live-Editor-Befehl ist abgelaufen.', 'wp-agent-companion' );
			$command['completed_at'] = $now;
			if ( ! wpagent_companion_live_editor_store_record( $option, $command ) ) {
				return new WP_Error( 'wpagent_live_editor_command_save', __( 'Ein abgelaufener Live-Editor-Befehl konnte nicht sicher abgeschlossen werden.', 'wp-agent-companion' ), array( 'status' => 500 ) );
			}
			$session = wpagent_companion_live_editor_archive_command( $session, $command_id );
		}
	}
	return $session;
}

/** @return array<string,mixed>|WP_Error */
function wpagent_companion_live_editor_queue_command( string $action, array $args, bool $requires_confirmation ) {
	$session = wpagent_companion_live_editor_agent_session();
	if ( is_wp_error( $session ) ) {
		return $session;
	}
	$session_id = (string) $session['session_id'];
	$lock       = wpagent_companion_live_editor_acquire_lock( $session_id );
	if ( is_wp_error( $lock ) ) {
		return $lock;
	}
	try {
		$session = wpagent_companion_live_editor_get_session( $session_id );
		if ( is_wp_error( $session ) ) {
			return $session;
		}
		$session = wpagent_companion_live_editor_prune_commands( $session );
		if ( is_wp_error( $session ) ) {
			return $session;
		}
		if ( ! in_array( $action, array( 'open-post', 'open-elementor-document' ), true ) ) {
			$args['expected_post_id'] = absint( $session['page']['post_id'] ?? 0 );
		}
		$access = wpagent_companion_live_editor_check_command_access( $session, $action, $args );
		if ( is_wp_error( $access ) ) {
			if ( ! wpagent_companion_live_editor_store_record( wpagent_companion_live_editor_session_option( $session_id ), $session ) ) {
				return new WP_Error( 'wpagent_live_editor_session_save', __( 'Die bereinigte Live-Editor-Sitzung konnte nicht sicher gespeichert werden.', 'wp-agent-companion' ), array( 'status' => 500 ) );
			}
			return $access;
		}
		$ids = array_values( array_filter( is_array( $session['command_ids'] ?? null ) ? $session['command_ids'] : array(), 'is_string' ) );
		if ( count( $ids ) >= WPAGENT_COMPANION_LIVE_EDITOR_MAX_PENDING ) {
			return new WP_Error( 'wpagent_live_editor_queue_full', __( 'Der Live-Editor hat bereits 20 offene Befehle. Warte auf ihre Bearbeitung.', 'wp-agent-companion' ), array( 'status' => 429 ) );
		}
		try {
			$command_id = bin2hex( random_bytes( 12 ) );
		} catch ( Throwable $error ) {
			return new WP_Error( 'wpagent_live_editor_entropy', __( 'Der sichere Live-Editor-Befehl konnte nicht erzeugt werden.', 'wp-agent-companion' ), array( 'status' => 500 ) );
		}
		$now     = time();
		$command = array(
			'command_id'           => $command_id,
			'session_id'           => $session_id,
			'executor_id'          => get_current_user_id(),
			'action'               => $action,
			'args'                 => $args,
			'requires_confirmation' => $requires_confirmation,
			'status'               => 'pending',
			'created_at'           => $now,
			'expires_at'           => $now + WPAGENT_COMPANION_LIVE_EDITOR_COMMAND_TTL,
			'result'               => array(),
			'error'                => '',
		);
		$command_option = wpagent_companion_live_editor_command_option( $command_id );
		if ( ! wpagent_companion_add_option_create_only( $command_option, $command, '', false ) ) {
			return new WP_Error( 'wpagent_live_editor_command_save', __( 'Der Live-Editor-Befehl konnte nicht gespeichert werden.', 'wp-agent-companion' ), array( 'status' => 500 ) );
		}
		$session['command_ids'][] = $command_id;
		if ( ! wpagent_companion_live_editor_store_record( wpagent_companion_live_editor_session_option( $session_id ), $session ) || get_option( $command_option, null ) !== $command ) {
			wpagent_companion_live_editor_delete_record( $command_option, $command );
			return new WP_Error( 'wpagent_live_editor_command_save', __( 'Der Live-Editor-Befehl konnte nicht verifiziert gespeichert werden.', 'wp-agent-companion' ), array( 'status' => 500 ) );
		}
		return wpagent_companion_live_editor_public_command( $command );
	} finally {
		wpagent_companion_live_editor_release_lock( $session_id, $lock );
	}
}

/** @return array<string,mixed> */
function wpagent_companion_live_editor_public_command( array $command ): array {
	return array(
		'command_id'           => (string) $command['command_id'],
		'action'               => (string) $command['action'],
		'status'               => (string) $command['status'],
		'requires_confirmation' => (bool) $command['requires_confirmation'],
		'created_at'           => gmdate( 'c', absint( $command['created_at'] ?? 0 ) ),
		'expires_at'           => gmdate( 'c', absint( $command['expires_at'] ?? 0 ) ),
		'result'               => is_array( $command['result'] ?? null ) ? $command['result'] : array(),
		'error'                => (string) ( $command['error'] ?? '' ),
	);
}

/** @return array<string,mixed> */
function wpagent_companion_live_editor_normalize_page( array $page ): array {
	$kind = is_string( $page['kind'] ?? null ) ? sanitize_key( $page['kind'] ) : 'empty';
	if ( ! in_array( $kind, array( 'empty', 'page', 'gutenberg', 'elementor', 'error' ), true ) ) {
		$kind = 'error';
	}
	$state_hash = is_string( $page['state_hash'] ?? null ) && preg_match( '/^[a-f0-9]{64}$/D', $page['state_hash'] ) ? $page['state_hash'] : '';
	return array(
		'kind'       => $kind,
		'title'      => wp_html_excerpt( sanitize_text_field( (string) ( $page['title'] ?? '' ) ), 200, '' ),
		'path'       => wp_html_excerpt( sanitize_text_field( (string) ( $page['path'] ?? '' ) ), 2048, '' ),
		'post_id'    => absint( $page['post_id'] ?? 0 ),
		'ready'      => true === ( $page['ready'] ?? false ),
		'dirty'      => true === ( $page['dirty'] ?? false ),
		'state_hash' => $state_hash,
	);
}

/** @return string|WP_Error */
function wpagent_companion_live_editor_new_execution_lease() {
	try {
		return bin2hex( random_bytes( 32 ) );
	} catch ( Throwable $error ) {
		return new WP_Error( 'wpagent_live_editor_entropy', __( 'Die sichere Ausführungsfreigabe konnte nicht erzeugt werden.', 'wp-agent-companion' ), array( 'status' => 500 ) );
	}
}


/** Erteilt unmittelbar vor einer bestätigten Browsermutation eine einmalige Ausführungsfreigabe. */
function wpagent_companion_live_editor_lease_command( string $session_id, string $command_id, array $page, array $active_content ) {
	$session = wpagent_companion_live_editor_get_session( $session_id );
	if ( is_wp_error( $session ) ) return $session;
	if ( ! current_user_can( 'manage_options' ) || get_current_user_id() !== (int) $session['owner_id'] ) {
		return new WP_Error( 'wpagent_live_editor_owner_required', __( 'Diese Live-Editor-Sitzung gehört zu einem anderen Administrator.', 'wp-agent-companion' ), array( 'status' => 403 ) );
	}
	$lock = wpagent_companion_live_editor_acquire_lock( $session_id );
	if ( is_wp_error( $lock ) ) return $lock;
	try {
		$session = wpagent_companion_live_editor_get_session( $session_id );
		if ( is_wp_error( $session ) ) return $session;
		$option  = wpagent_companion_live_editor_command_option( $command_id );
		$command = get_option( $option, null );
		if ( ! is_array( $command ) || ! hash_equals( $session_id, (string) ( $command['session_id'] ?? '' ) ) ) {
			return new WP_Error( 'wpagent_live_editor_command_missing', __( 'Der Live-Editor-Befehl wurde nicht gefunden.', 'wp-agent-companion' ), array( 'status' => 404 ) );
		}
		if ( 'awaiting_confirmation' !== (string) ( $command['status'] ?? '' ) || empty( $command['requires_confirmation'] ) ) {
			return new WP_Error( 'wpagent_live_editor_command_state', __( 'Der Live-Editor-Befehl kann nicht erneut ausgeführt werden.', 'wp-agent-companion' ), array( 'status' => 409 ) );
		}
		if ( absint( $command['expires_at'] ?? 0 ) <= time() ) {
			return new WP_Error( 'wpagent_live_editor_command_expired', __( 'Der Live-Editor-Befehl ist abgelaufen.', 'wp-agent-companion' ), array( 'status' => 410 ) );
		}
		$access = wpagent_companion_live_editor_check_command_access( $session, (string) $command['action'], is_array( $command['args'] ?? null ) ? $command['args'] : array() );
		if ( is_wp_error( $access ) ) return $access;
		$normalized_page = wpagent_companion_live_editor_normalize_page( $page );
		if ( 'open-post' !== (string) $command['action'] ) {
			$expected_post = absint( $command['args']['expected_post_id'] ?? 0 );
			$expected_hash = (string) ( $command['args']['expected_state_hash'] ?? '' );
			$stored_page   = is_array( $session['page'] ?? null ) ? $session['page'] : array();
			if ( true !== $normalized_page['ready'] || 'gutenberg' !== $normalized_page['kind']
				|| $expected_post !== $normalized_page['post_id'] || ! hash_equals( $expected_hash, $normalized_page['state_hash'] )
				|| $expected_post !== absint( $stored_page['post_id'] ?? 0 ) || ! hash_equals( $expected_hash, (string) ( $stored_page['state_hash'] ?? '' ) ) ) {
				return new WP_Error( 'wpagent_live_editor_state_conflict', __( 'Beitrag oder Editorzustand haben sich seit dem Einreihen geändert.', 'wp-agent-companion' ), array( 'status' => 409 ) );
			}
			$content_hash = is_string( $active_content['state_hash'] ?? null ) ? $active_content['state_hash'] : '';
			if ( ! hash_equals( $expected_hash, $content_hash ) ) {
				return new WP_Error( 'wpagent_live_editor_state_conflict', __( 'Die aktive Blockstruktur gehört nicht zum bestätigten Editorzustand.', 'wp-agent-companion' ), array( 'status' => 409 ) );
			}
		}
		$valid = wpagent_companion_live_editor_validate_active_content( $session, $command, $active_content );
		if ( is_wp_error( $valid ) ) return $valid;
		$lease = wpagent_companion_live_editor_new_execution_lease();
		if ( is_wp_error( $lease ) ) return $lease;
		$leased                         = $command;
		$leased['status']               = 'running';
		$leased['execution_lease_hash'] = hash( 'sha256', $lease );
		$leased['leased_at']            = time();
		if ( ! wpagent_companion_live_editor_compare_and_store_record( $option, $command, $leased ) ) {
			return new WP_Error( 'wpagent_live_editor_command_state', __( 'Der Live-Editor-Befehl wurde bereits anderweitig übernommen.', 'wp-agent-companion' ), array( 'status' => 409 ) );
		}
		return array_merge( wpagent_companion_live_editor_public_command( $leased ), array( 'args' => $leased['args'], 'execution_lease' => $lease ) );
	} finally {
		wpagent_companion_live_editor_release_lock( $session_id, $lock );
	}
}

/** @return array<string,mixed>|WP_Error */
function wpagent_companion_live_editor_claim_next( string $session_id ) {
	$session = wpagent_companion_live_editor_get_session( $session_id );
	if ( is_wp_error( $session ) ) {
		return $session;
	}
	if ( ! current_user_can( 'manage_options' ) || get_current_user_id() !== (int) $session['owner_id'] ) {
		return new WP_Error( 'wpagent_live_editor_owner_required', __( 'Diese Live-Editor-Sitzung gehört zu einem anderen Administrator.', 'wp-agent-companion' ), array( 'status' => 403 ) );
	}
	$lock = wpagent_companion_live_editor_acquire_lock( $session_id );
	if ( is_wp_error( $lock ) ) {
		return $lock;
	}
	try {
		$session = wpagent_companion_live_editor_get_session( $session_id );
		if ( is_wp_error( $session ) ) {
			return $session;
		}
		$executor = wpagent_companion_live_editor_executor( $session );
		if ( is_wp_error( $executor ) ) {
			wpagent_companion_live_editor_delete_session_record( $session );
			return $executor;
		}
		$session = wpagent_companion_live_editor_prune_commands( $session );
		if ( is_wp_error( $session ) ) {
			return $session;
		}
		foreach ( $session['command_ids'] as $command_id ) {
			$option  = wpagent_companion_live_editor_command_option( (string) $command_id );
			$command = get_option( $option, null );
			if ( ! is_array( $command ) || 'pending' !== (string) ( $command['status'] ?? '' ) ) {
				continue;
			}
			$access = wpagent_companion_live_editor_check_command_access( $session, (string) ( $command['action'] ?? '' ), is_array( $command['args'] ?? null ) ? $command['args'] : array() );
			if ( is_wp_error( $access ) ) {
				$command['status']       = 'failed';
				$command['error']        = $access->get_error_message();
				$command['completed_at'] = time();
				if ( ! wpagent_companion_live_editor_store_record( $option, $command ) ) {
					return new WP_Error( 'wpagent_live_editor_command_save', __( 'Der abgelehnte Live-Editor-Befehl konnte nicht sicher abgeschlossen werden.', 'wp-agent-companion' ), array( 'status' => 500 ) );
				}
				$session = wpagent_companion_live_editor_archive_command( $session, (string) $command_id );
				continue;
			}
			if ( ! wpagent_companion_live_editor_store_record( wpagent_companion_live_editor_session_option( $session_id ), $session ) ) {
				return new WP_Error( 'wpagent_live_editor_session_save', __( 'Die Live-Editor-Warteschlange konnte nicht sicher gespeichert werden.', 'wp-agent-companion' ), array( 'status' => 500 ) );
			}
			$claimed           = $command;
			$claimed['status'] = ! empty( $command['requires_confirmation'] ) ? 'awaiting_confirmation' : 'running';
			$execution_lease   = '';
			if ( empty( $command['requires_confirmation'] ) ) {
				$execution_lease = wpagent_companion_live_editor_new_execution_lease();
				if ( is_wp_error( $execution_lease ) ) return $execution_lease;
				$claimed['execution_lease_hash'] = hash( 'sha256', $execution_lease );
				$claimed['leased_at']            = time();
			}
			if ( ! wpagent_companion_live_editor_compare_and_store_record( $option, $command, $claimed ) ) {
				return new WP_Error( 'wpagent_live_editor_command_save', __( 'Der Live-Editor-Befehl konnte nicht sicher übernommen werden.', 'wp-agent-companion' ), array( 'status' => 500 ) );
			}
			$response = array_merge( wpagent_companion_live_editor_public_command( $claimed ), array( 'args' => $claimed['args'] ) );
			if ( '' !== $execution_lease ) $response['execution_lease'] = $execution_lease;
			return $response;
		}
		if ( ! wpagent_companion_live_editor_store_record( wpagent_companion_live_editor_session_option( $session_id ), $session ) ) {
			return new WP_Error( 'wpagent_live_editor_session_save', __( 'Die Live-Editor-Warteschlange konnte nicht sicher gespeichert werden.', 'wp-agent-companion' ), array( 'status' => 500 ) );
		}
		return array( 'status' => 'idle' );
	} finally {
		wpagent_companion_live_editor_release_lock( $session_id, $lock );
	}
}

/** @return array<string,mixed>|WP_Error */
function wpagent_companion_live_editor_complete_command( string $session_id, string $command_id, string $status, array $result, string $error, string $execution_lease = '' ) {
	$session = wpagent_companion_live_editor_get_session( $session_id );
	if ( is_wp_error( $session ) ) {
		return $session;
	}
	if ( ! current_user_can( 'manage_options' ) || get_current_user_id() !== (int) $session['owner_id'] ) {
		return new WP_Error( 'wpagent_live_editor_owner_required', __( 'Diese Live-Editor-Sitzung gehört zu einem anderen Administrator.', 'wp-agent-companion' ), array( 'status' => 403 ) );
	}
	if ( ! in_array( $status, array( 'completed', 'failed', 'denied' ), true ) ) {
		return new WP_Error( 'wpagent_live_editor_result_status', __( 'Unbekannter Live-Editor-Ergebnisstatus.', 'wp-agent-companion' ), array( 'status' => 400 ) );
	}
	if ( ! preg_match( '/^[a-f0-9]{24}$/D', $command_id ) || ( '' !== $execution_lease && ! preg_match( '/^[a-f0-9]{64}$/D', $execution_lease ) ) ) {
		return new WP_Error( 'wpagent_live_editor_result_identity', __( 'Befehl oder Ausführungsfreigabe sind ungültig.', 'wp-agent-companion' ), array( 'status' => 400 ) );
	}
	if ( strlen( $error ) > WPAGENT_COMPANION_LIVE_EDITOR_MAX_ERROR_BYTES ) {
		return new WP_Error( 'wpagent_live_editor_error_too_large', __( 'Die Live-Editor-Fehlermeldung ist größer als 10.000 Byte.', 'wp-agent-companion' ), array( 'status' => 413 ) );
	}
	$encoded = wp_json_encode( $result );
	if ( ! is_string( $encoded ) || strlen( $encoded ) > WPAGENT_COMPANION_LIVE_EDITOR_MAX_RESULT_BYTES ) {
		return new WP_Error( 'wpagent_live_editor_result_too_large', __( 'Das Live-Editor-Ergebnis ist größer als 100.000 Byte.', 'wp-agent-companion' ), array( 'status' => 413 ) );
	}
	$lock = wpagent_companion_live_editor_acquire_lock( $session_id );
	if ( is_wp_error( $lock ) ) {
		return $lock;
	}
	try {
		$session = wpagent_companion_live_editor_get_session( $session_id );
		if ( is_wp_error( $session ) ) {
			return $session;
		}
		$option  = wpagent_companion_live_editor_command_option( $command_id );
		$command = get_option( $option, null );
		if ( ! is_array( $command ) || ! hash_equals( $session_id, (string) ( $command['session_id'] ?? '' ) ) ) {
			return new WP_Error( 'wpagent_live_editor_command_missing', __( 'Der Live-Editor-Befehl wurde nicht gefunden.', 'wp-agent-companion' ), array( 'status' => 404 ) );
		}
		$current_status = (string) ( $command['status'] ?? '' );
		$lease_hash     = is_string( $command['execution_lease_hash'] ?? null ) ? $command['execution_lease_hash'] : '';
		if ( in_array( $current_status, array( 'completed', 'failed', 'denied', 'recovery_required' ), true ) ) {
			if ( '' !== $lease_hash && ( '' === $execution_lease || ! hash_equals( $lease_hash, hash( 'sha256', $execution_lease ) ) ) ) {
				return new WP_Error( 'wpagent_live_editor_lease_invalid', __( 'Die Ausführungsfreigabe gehört nicht zu diesem Befehl.', 'wp-agent-companion' ), array( 'status' => 409 ) );
			}
			return wpagent_companion_live_editor_public_command( $command );
		}
		// Browseraktionen werden clientseitig ausgeführt. Läuft eine bereits
		// vergebene Ausführungsfreigabe ab, darf ihr Ergebnis daher nicht mehr
		// stillschweigend als regulärer Abschluss gespeichert werden. Der
		// tatsächliche Editorzustand muss anschließend geprüft werden.
		if ( 'running' === $current_status && absint( $command['expires_at'] ?? 0 ) <= time() ) {
			if ( '' === $lease_hash || '' === $execution_lease || ! hash_equals( $lease_hash, hash( 'sha256', $execution_lease ) ) ) {
				return new WP_Error( 'wpagent_live_editor_lease_invalid', __( 'Das Ergebnis besitzt keine gültige einmalige Ausführungsfreigabe.', 'wp-agent-companion' ), array( 'status' => 409 ) );
			}
			$recovery                 = $command;
			$recovery['status']       = 'recovery_required';
			$recovery['result']       = array();
			$recovery['error']        = __( 'Der Live-Editor-Befehl ist abgelaufen.', 'wp-agent-companion' );
			$recovery['completed_at'] = time();
			if ( ! wpagent_companion_live_editor_compare_and_store_record( $option, $command, $recovery ) ) {
				return new WP_Error( 'wpagent_live_editor_command_save', __( 'Das Live-Editor-Ergebnis konnte nicht verifiziert gespeichert werden.', 'wp-agent-companion' ), array( 'status' => 500 ) );
			}
			$session = wpagent_companion_live_editor_archive_command( $session, $command_id );
			if ( ! wpagent_companion_live_editor_store_record( wpagent_companion_live_editor_session_option( $session_id ), $session ) ) {
				return new WP_Error( 'wpagent_live_editor_session_save', __( 'Das Live-Editor-Ergebnis wurde gespeichert, aber die Sitzung konnte nicht sicher aktualisiert werden.', 'wp-agent-companion' ), array( 'status' => 500 ) );
			}
			return wpagent_companion_live_editor_public_command( $recovery );
		}
		if ( 'denied' === $status ) {
			if ( 'awaiting_confirmation' !== $current_status || '' !== $lease_hash ) {
				return new WP_Error( 'wpagent_live_editor_command_state', __( 'Nur ein noch nicht ausgeführter Bestätigungsbefehl kann abgelehnt werden.', 'wp-agent-companion' ), array( 'status' => 409 ) );
			}
		} elseif ( 'running' !== $current_status || '' === $lease_hash
			|| '' === $execution_lease || ! hash_equals( $lease_hash, hash( 'sha256', $execution_lease ) ) ) {
			return new WP_Error( 'wpagent_live_editor_lease_invalid', __( 'Das Ergebnis besitzt keine gültige einmalige Ausführungsfreigabe.', 'wp-agent-companion' ), array( 'status' => 409 ) );
		}
		if ( ! in_array( $current_status, array( 'running', 'awaiting_confirmation' ), true ) ) {
			return new WP_Error( 'wpagent_live_editor_command_state', __( 'Der Live-Editor-Befehl nimmt kein Ergebnis mehr an.', 'wp-agent-companion' ), array( 'status' => 409 ) );
		}
		$completed                 = $command;
		$completed['status']       = $status;
		$completed['result']       = $result;
		$completed['error']        = wp_html_excerpt( wp_strip_all_tags( $error ), 2000, '' );
		$completed['completed_at'] = time();
		if ( ! wpagent_companion_live_editor_compare_and_store_record( $option, $command, $completed ) ) {
			return new WP_Error( 'wpagent_live_editor_command_save', __( 'Das Live-Editor-Ergebnis konnte nicht verifiziert gespeichert werden.', 'wp-agent-companion' ), array( 'status' => 500 ) );
		}
		$session = wpagent_companion_live_editor_archive_command( $session, $command_id );
		$session_option = wpagent_companion_live_editor_session_option( $session_id );
		if ( ! wpagent_companion_live_editor_store_record( $session_option, $session ) ) {
			return new WP_Error( 'wpagent_live_editor_session_save', __( 'Das Live-Editor-Ergebnis wurde gespeichert, aber die Sitzung konnte nicht sicher aktualisiert werden.', 'wp-agent-companion' ), array( 'status' => 500 ) );
		}
		return wpagent_companion_live_editor_public_command( $completed );
	} finally {
		wpagent_companion_live_editor_release_lock( $session_id, $lock );
	}
}

/** @return array<string,mixed>|WP_Error */
function wpagent_companion_live_editor_heartbeat( string $session_id, array $page = array() ) {
	$session = wpagent_companion_live_editor_get_session( $session_id );
	if ( is_wp_error( $session ) ) {
		return $session;
	}
	if ( ! current_user_can( 'manage_options' ) || get_current_user_id() !== (int) $session['owner_id'] ) {
		return new WP_Error( 'wpagent_live_editor_owner_required', __( 'Diese Live-Editor-Sitzung gehört zu einem anderen Administrator.', 'wp-agent-companion' ), array( 'status' => 403 ) );
	}
	$lock = wpagent_companion_live_editor_acquire_lock( $session_id );
	if ( is_wp_error( $lock ) ) {
		return $lock;
	}
	try {
		$session = wpagent_companion_live_editor_get_session( $session_id );
		if ( is_wp_error( $session ) ) {
			return $session;
		}
		$normalized_page = wpagent_companion_live_editor_normalize_page( $page );
		$trusted_page = is_array( $session['page'] ?? null ) ? $session['page'] : array();
		if ( 'elementor' === (string) ( $trusted_page['kind'] ?? '' ) ) {
			// Die Navigation des same-origin iframe löst während des Ladens einen
			// Heartbeat mit einer leeren oder generischen Seite aus. Dieser darf die
			// serverseitig bestätigte Elementor-Dokumentbindung nicht ersetzen. Bis
			// derselbe Beitrag wieder als Elementor-Frame bestätigt ist, bleibt die
			// Sitzung bewusst nicht beschreibbar.
			if ( 'elementor' !== $normalized_page['kind'] || $normalized_page['post_id'] !== absint( $trusted_page['post_id'] ?? 0 ) ) {
				$normalized_page          = $trusted_page;
				$normalized_page['ready'] = false;
				$normalized_page['dirty'] = false;
			} else {
				$normalized_page['title']      = (string) ( $trusted_page['title'] ?? '' );
				$normalized_page['path']       = (string) ( $trusted_page['path'] ?? '' );
				$normalized_page['state_hash'] = (string) ( $trusted_page['state_hash'] ?? '' );
			}
		}
		$session['page']         = $normalized_page;
		$session['last_seen_at'] = time();
		$session['expires_at']   = time() + WPAGENT_COMPANION_LIVE_EDITOR_SESSION_TTL;
		$option = wpagent_companion_live_editor_session_option( $session_id );
		return wpagent_companion_live_editor_store_record( $option, $session )
			? wpagent_companion_live_editor_public_session( $session )
			: new WP_Error( 'wpagent_live_editor_session_save', __( 'Der Live-Editor-Heartbeat konnte nicht verifiziert gespeichert werden.', 'wp-agent-companion' ), array( 'status' => 500 ) );
	} finally {
		wpagent_companion_live_editor_release_lock( $session_id, $lock );
	}
}

/** @return array{closed:bool}|WP_Error */
function wpagent_companion_live_editor_close_session( string $session_id ) {
	$session = wpagent_companion_live_editor_get_session( $session_id );
	if ( is_wp_error( $session ) ) {
		return $session;
	}
	if ( ! current_user_can( 'manage_options' ) || get_current_user_id() !== (int) $session['owner_id'] ) {
		return new WP_Error( 'wpagent_live_editor_owner_required', __( 'Diese Live-Editor-Sitzung gehört zu einem anderen Administrator.', 'wp-agent-companion' ), array( 'status' => 403 ) );
	}
	$lock = wpagent_companion_live_editor_acquire_lock( $session_id );
	if ( is_wp_error( $lock ) ) {
		return $lock;
	}
	try {
		$session = wpagent_companion_live_editor_get_session( $session_id );
		if ( is_wp_error( $session ) ) {
			return $session;
		}
		wpagent_companion_live_editor_delete_session_record( $session );
		$index = get_option( wpagent_companion_live_editor_user_option( absint( $session['executor_id'] ?? 0 ) ), false );
		if ( false !== get_option( wpagent_companion_live_editor_session_option( $session_id ), false )
			|| ( is_string( $index ) && hash_equals( $session_id, $index ) ) ) {
			return new WP_Error( 'wpagent_live_editor_session_close', __( 'Die Live-Editor-Sitzung konnte nicht sicher beendet werden.', 'wp-agent-companion' ), array( 'status' => 500 ) );
		}
		return array( 'closed' => true );
	} finally {
		wpagent_companion_live_editor_release_lock( $session_id, $lock );
	}
}
