<?php
/**
 * Ability: wp-agent/switch-theme
 *
 * Wechselt das aktive Theme über switch_theme(). Zwei WordPress-Kern-
 * Fallstricke werden vorab abgefangen, statt switch_theme() blind
 * aufzurufen:
 *
 * 1. wp_get_theme( $stylesheet )->exists() ist die im Auftrag geforderte
 *    Mindestprüfung, fängt aber nur den Fall "Ordner/Theme existiert gar
 *    nicht" ab (Fehlercode theme_not_found). Ein Theme kann jedoch
 *    existieren und trotzdem kaputt sein (fehlende style.css, nicht lesbar,
 *    Child-Theme ohne vorhandenes Parent-Theme, pausiert nach einem
 *    früheren Fatal) -- das meldet WP_Theme::errors() zusätzlich, exists()
 *    dagegen nicht. Ein Wechsel auf so ein Theme würde das Frontend sofort
 *    brechen (genau das Risiko aus dem Auftrag), deshalb wird zusätzlich
 *    errors() geprüft.
 * 2. switch_theme() ruft intern validate_theme_requirements() auf und bei
 *    nicht erfüllten WP-/PHP-Mindestanforderungen (Requires at
 *    least/Requires PHP im Theme-Header) direkt wp_die() auf, statt einen
 *    WP_Error zurückzugeben. wp_die() ist in einem JSON-REST-Kontext ein
 *    härterer, schlechter diagnostizierbarer Fehlschlag als eine saubere
 *    Fehlerantwort. Diese Ability führt dieselbe Prüfung deshalb selbst
 *    vorab aus und ruft switch_theme() bei einem Fehlschlag gar nicht erst
 *    auf.
 *
 * @package WPAgentCompanion
 */

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

define( 'WPAGENT_SWITCH_THEME_LOCK_OPTION', 'wp_agent_switch_theme_lock' );
define( 'WPAGENT_SWITCH_THEME_LOCK_TTL', 300 );

/** @return array{template:string,stylesheet:string} */
function wpagent_switch_theme_state(): array {
	return array(
		'template'   => (string) get_template(),
		'stylesheet' => (string) get_stylesheet(),
	);
}

function wpagent_switch_theme_state_matches( array $left, array $right ): bool {
	return (string) ( $left['template'] ?? '' ) === (string) ( $right['template'] ?? '' )
		&& (string) ( $left['stylesheet'] ?? '' ) === (string) ( $right['stylesheet'] ?? '' );
}

/** @return string|WP_Error */
function wpagent_switch_theme_acquire_lock() {
	$token = wpagent_companion_acquire_option_lock( WPAGENT_SWITCH_THEME_LOCK_OPTION, WPAGENT_SWITCH_THEME_LOCK_TTL );
	if ( is_string( $token ) ) {
		return $token;
	}
	return new WP_Error( 'wpagent_switch_theme_busy', __( 'Ein anderer Theme-Wechsel läuft bereits. Versuche es gleich erneut.', 'wp-agent-companion' ), array( 'status' => 409 ) );
}

function wpagent_switch_theme_release_lock( string $token ): void {
	wpagent_companion_release_option_lock( WPAGENT_SWITCH_THEME_LOCK_OPTION, $token );
}

/**
 * Rolls back only while the active pair is still the state written by this
 * operation. A concurrent third-party theme change is never overwritten.
 */
function wpagent_switch_theme_fail_after_safe_rollback( array $previous, array $target, WP_Error $failure ): WP_Error {
	$current = wpagent_switch_theme_state();
	if ( wpagent_switch_theme_state_matches( $current, $previous ) ) {
		return $failure;
	}
	if ( ! wpagent_switch_theme_state_matches( $current, $target ) ) {
		return new WP_Error(
			'wpagent_switch_theme_recovery_required',
			__( 'Der Theme-Zustand wurde während des fehlgeschlagenen Wechsels erneut verändert. Der neuere Zustand bleibt erhalten und muss geprüft werden.', 'wp-agent-companion' ),
			array( 'status' => 500, 'original_error' => $failure->get_error_code(), 'current' => $current )
		);
	}
	try {
		switch_theme( (string) $previous['stylesheet'] );
	} catch ( \Throwable ) {
		// Der abschließende Zustandsvergleich entscheidet, ob der native
		// Theme-Wechsel trotz der Hook-Ausnahme vollständig zurückgerollt hat.
	}
	if ( wpagent_switch_theme_state_matches( wpagent_switch_theme_state(), $previous ) ) {
		return $failure;
	}
	return new WP_Error(
		'wpagent_switch_theme_rollback_failed',
		__( 'Der Theme-Wechsel ist fehlgeschlagen und der vorherige Template-/Stylesheet-Zustand konnte nicht vollständig wiederhergestellt werden.', 'wp-agent-companion' ),
		array( 'status' => 500, 'original_error' => $failure->get_error_code() )
	);
}

function wpagent_ability_switch_theme(): array {
	return array(
		'label'               => __( 'Theme wechseln', 'wp-agent-companion' ),
		'description'         => __( 'Wechselt das aktive Theme unter einem Site-Lock. Lehnt unbekannte, fehlerhafte oder inkompatible Themes ab, verifiziert Template und Stylesheet und rollt Hook-Fehler sicher zurück.', 'wp-agent-companion' ),
		'category'            => 'wp-agent',
		'input_schema'        => array(
			'type'       => 'object',
			'properties' => array(
				'stylesheet' => array(
					'type'        => 'string',
					'description' => __( 'Ordnername des Themes (Stylesheet), z. B. "twentytwentyfive".', 'wp-agent-companion' ),
				),
			),
			'required'   => array( 'stylesheet' ),
		),
		'output_schema'       => array(
			'type'       => 'object',
			'properties' => array(
				'stylesheet'          => array( 'type' => 'string' ),
				'previous_stylesheet' => array( 'type' => 'string' ),
				'verified'            => array( 'type' => 'boolean' ),
			),
		),
		'execute_callback'    => 'wpagent_exec_switch_theme',
		'permission_callback' => 'wpagent_perm_switch_themes',
		'meta'                => array(
			'show_in_rest' => true,
			'annotations'  => array(
				'readonly'    => false,
				'destructive' => false,
				'idempotent'  => true,
			),
		),
	);
}

/**
 * @param mixed $input
 * @return array|WP_Error
 */
function wpagent_exec_switch_theme( $input ) {
	$input      = is_array( $input ) ? $input : array();
	$stylesheet = is_string( $input['stylesheet'] ?? null ) ? $input['stylesheet'] : '';

	if ( '' === trim( $stylesheet ) ) {
		return new WP_Error( 'wpagent_missing_stylesheet', __( 'stylesheet fehlt.', 'wp-agent-companion' ), array( 'status' => 400 ) );
	}

	$theme = wp_get_theme( $stylesheet );
	if ( ! $theme->exists() ) {
		return new WP_Error( 'wpagent_no_theme', __( 'Theme nicht gefunden.', 'wp-agent-companion' ), array( 'status' => 404 ) );
	}

	// Siehe Datei-Kommentar Punkt 1: existiert, aber trotzdem kaputt.
	$theme_errors = $theme->errors();
	if ( is_wp_error( $theme_errors ) ) {
		return new WP_Error(
			'wpagent_broken_theme',
			sprintf(
				/* translators: 1: Theme-Stylesheet, 2: Fehlermeldung. */
				__( 'Theme "%1$s" ist fehlerhaft und wird nicht aktiviert: %2$s', 'wp-agent-companion' ),
				$stylesheet,
				$theme_errors->get_error_message()
			),
			array( 'status' => 400 )
		);
	}

	// Siehe Datei-Kommentar Punkt 2: WP-/PHP-Mindestanforderungen vorab
	// prüfen, statt switch_theme() intern wp_die() auslösen zu lassen.
	$requirements = validate_theme_requirements( $stylesheet );
	if ( is_wp_error( $requirements ) ) {
		return new WP_Error( 'wpagent_theme_requirements', $requirements->get_error_message(), array( 'status' => 400 ) );
	}

	$lock = wpagent_switch_theme_acquire_lock();
	if ( is_wp_error( $lock ) ) {
		return $lock;
	}
	try {
		return wpagent_exec_switch_theme_locked( $stylesheet, $theme );
	} finally {
		wpagent_switch_theme_release_lock( $lock );
	}
}

/** @return array|WP_Error */
function wpagent_exec_switch_theme_locked( string $stylesheet, WP_Theme $theme ) {
	$previous            = wpagent_switch_theme_state();
	$previous_stylesheet = $previous['stylesheet'];
	$target              = array(
		'template'   => (string) $theme->get_template(),
		'stylesheet' => $stylesheet,
	);
	if ( wpagent_switch_theme_state_matches( $previous, $target ) ) {
		return array(
			'stylesheet'          => $stylesheet,
			'previous_stylesheet' => $previous_stylesheet,
			'verified'            => true,
		);
	}

	try {
		switch_theme( $stylesheet );
	} catch ( \Throwable $e ) {
		// Ein fehlerhafter switch_theme-Hook des Ziel-Themes könnte
		// sonst den ganzen Request mitreißen; siehe activate-plugin.php für
		// dieselbe Begründung bei Plugin-Hooks.
		$failure = new WP_Error(
			'wpagent_switch_fatal',
			sprintf(
				/* translators: 1: Theme-Stylesheet, 2: Fehlermeldung. */
				__( 'Theme-Wechsel zu "%1$s" hat einen fatalen Fehler ausgelöst: %2$s', 'wp-agent-companion' ),
				$stylesheet,
				$e->getMessage()
			),
			array( 'status' => 500 )
		);
		return wpagent_switch_theme_fail_after_safe_rollback( $previous, $target, $failure );
	}

	$fresh_state = wpagent_switch_theme_state();
	$verified    = wpagent_switch_theme_state_matches( $fresh_state, $target );
	if ( ! $verified ) {
		return wpagent_switch_theme_fail_after_safe_rollback(
			$previous,
			$target,
			new WP_Error( 'wpagent_switch_verify_failed', __( 'Der Theme-Wechsel bestand die Template-/Stylesheet-Rückleseprüfung nicht.', 'wp-agent-companion' ), array( 'status' => 500 ) )
		);
	}

	return array(
		'stylesheet'          => $fresh_state['stylesheet'],
		'previous_stylesheet' => $previous_stylesheet,
		'verified'            => $verified,
	);
}
