<?php
/**
 * Sichere Freigabe der nativen WPForms-Abilities über den wpagently-MCP-Server.
 *
 * WPForms bleibt Eigentümer der Schemas, Ausführung, Schreibfreigabe und
 * Berechtigungsprüfungen. wpagently gruppiert lediglich die bekannten nativen
 * Abilities und respektiert dabei den aktuellen meta.mcp.public-Status.
 *
 * @package WPAgentCompanion
 */

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

define( 'WPAGENT_COMPANION_OPT_WPFORMS_GRANTS', 'wp_agent_companion_wpforms_capability_grants' );

/** @return array{forms:string,entries:string,write:string} */
function wpagent_companion_wpforms_access_capabilities(): array {
	return array(
		'forms'   => 'wpagently_wpforms_forms_access',
		'entries' => 'wpagently_wpforms_entries_access',
		'write'   => 'wpagently_wpforms_write_access',
	);
}

/** @return array<int,string[]> */
function wpagent_companion_wpforms_capability_grants(): array {
	$stored  = get_option( WPAGENT_COMPANION_OPT_WPFORMS_GRANTS, array() );
	$allowed = array_values( wpagent_companion_wpforms_access_capabilities() );
	$grants  = array();
	foreach ( is_array( $stored ) ? $stored : array() as $user_id => $capabilities ) {
		$user_id = absint( $user_id );
		if ( ! $user_id || ! is_array( $capabilities ) ) {
			continue;
		}
		$capabilities = array_values( array_unique( array_intersect( $allowed, array_map( 'sanitize_key', $capabilities ) ) ) );
		if ( $capabilities ) {
			$grants[ $user_id ] = $capabilities;
		}
	}
	return array_slice( $grants, 0, 200, true );
}

/** @param array<int,string[]> $grants */
function wpagent_companion_wpforms_store_capability_grants( array $grants ): bool {
	if ( $grants ) {
		update_option( WPAGENT_COMPANION_OPT_WPFORMS_GRANTS, $grants, false );
	} else {
		delete_option( WPAGENT_COMPANION_OPT_WPFORMS_GRANTS );
	}
	return wpagent_companion_wpforms_capability_grants() === $grants;
}

/**
 * @return array{enabled:bool,capability:string,tracked:bool}|WP_Error
 */
function wpagent_companion_wpforms_user_access( int $user_id, string $group ) {
	$capabilities = wpagent_companion_wpforms_access_capabilities();
	if ( ! isset( $capabilities[ $group ] ) ) {
		return new WP_Error( 'wpagent_wpforms_invalid_group', __( 'Unbekannte WPForms-Rechtegruppe.', 'wp-agent-companion' ), array( 'status' => 400 ) );
	}
	$user = get_user_by( 'id', $user_id );
	if ( ! $user instanceof WP_User || ! wpagent_companion_is_safe_connection_user( $user ) ) {
		return new WP_Error( 'wpagent_wpforms_unsafe_user', __( 'Wähle einen Redakteur ohne Administrator- oder Super-Administratorrechte.', 'wp-agent-companion' ), array( 'status' => 400 ) );
	}
	$grants     = wpagent_companion_wpforms_capability_grants();
	$capability = $capabilities[ $group ];
	return array(
		'enabled'    => user_can( $user, $capability ),
		'capability' => $capability,
		'tracked'    => in_array( $capability, $grants[ $user_id ] ?? array(), true ),
	);
}

/**
 * Ergänzt oder entfernt genau eine wpagently-eigene WPForms-Rechtegruppe.
 * Bereits vorher vorhandene Rechte werden nie als eigener Grant protokolliert
 * und deshalb beim Deaktivieren nicht entfernt.
 *
 * @return array{enabled:bool,capability:string,tracked:bool}|WP_Error
 */
function wpagent_companion_wpforms_set_user_access( int $user_id, string $group, bool $enabled ) {
	$capabilities = wpagent_companion_wpforms_access_capabilities();
	if ( ! isset( $capabilities[ $group ] ) ) {
		return new WP_Error( 'wpagent_wpforms_invalid_group', __( 'Unbekannte WPForms-Rechtegruppe.', 'wp-agent-companion' ), array( 'status' => 400 ) );
	}
	$user = get_user_by( 'id', $user_id );
	if ( ! $user instanceof WP_User || ! wpagent_companion_is_safe_connection_user( $user ) ) {
		return new WP_Error( 'wpagent_wpforms_unsafe_user', __( 'Wähle einen Redakteur ohne Administrator- oder Super-Administratorrechte.', 'wp-agent-companion' ), array( 'status' => 400 ) );
	}
	$status         = wpagent_companion_wpforms_integration_status();
	$registered_key = 'registered_' . $group;
	if ( $enabled && empty( $status[ $registered_key ] ) ) {
		return new WP_Error( 'wpagent_wpforms_missing', __( 'WPForms ist nicht aktiv oder stellt keine nativen WordPress-Abilities bereit.', 'wp-agent-companion' ), array( 'status' => 400 ) );
	}

	$lock = wpagent_companion_acquire_connection_lock();
	if ( is_wp_error( $lock ) ) {
		return $lock;
	}

	try {
		$capability      = $capabilities[ $group ];
		$grants          = wpagent_companion_wpforms_capability_grants();
		$original_grants = $grants;
		$tracked         = $grants[ $user_id ] ?? array();
		$original_direct = ! empty( $user->caps[ $capability ] );
		$expected_direct = $original_direct;

		if ( $enabled ) {
			if ( in_array( $capability, $tracked, true ) && ! $original_direct ) {
				// Repair a tracked grant that another component removed directly.
				$user->add_cap( $capability );
				$expected_direct = true;
			} elseif ( ! user_can( $user, $capability ) ) {
				$user->add_cap( $capability );
				$tracked[] = $capability;
				$expected_direct = true;
			}
		} elseif ( in_array( $capability, $tracked, true ) ) {
			$user->remove_cap( $capability );
			$tracked = array_values( array_diff( $tracked, array( $capability ) ) );
			$expected_direct = false;
		}

		if ( $tracked ) {
			$grants[ $user_id ] = array_values( array_unique( $tracked ) );
		} else {
			unset( $grants[ $user_id ] );
		}
		clean_user_cache( $user_id );

		$fresh_user   = get_user_by( 'id', $user_id );
		$actual_direct = $fresh_user instanceof WP_User && ! empty( $fresh_user->caps[ $capability ] );
		if ( $actual_direct !== $expected_direct || ! wpagent_companion_wpforms_store_capability_grants( $grants ) ) {
			if ( $original_direct ) {
				$user->add_cap( $capability );
			} else {
				$user->remove_cap( $capability );
			}
			wpagent_companion_wpforms_store_capability_grants( $original_grants );
			clean_user_cache( $user_id );
			return new WP_Error( 'wpagent_wpforms_grant_failed', __( 'Die WPForms-Rechte konnten nicht sicher gespeichert werden.', 'wp-agent-companion' ), array( 'status' => 500 ) );
		}

		return wpagent_companion_wpforms_user_access( $user_id, $group );
	} finally {
		wpagent_companion_release_connection_lock( $lock );
	}
}

/**
 * Begrenzt die ergänzten WPForms-Rechte auf den eigenen MCP-Endpunkt.
 *
 * Der WPForms-Filter ist global. Ohne diese Begrenzung würden die drei
 * wpagently-Rechte auch normale WPForms-Admin-, AJAX- oder Fremd-REST-Pfade
 * freischalten. Der exakte Request-Abgleich unterstützt sowohl Pretty
 * Permalinks als auch ?rest_route= und wird bereits für die OAuth-
 * Ressourcenbindung verwendet.
 */
function wpagent_companion_wpforms_is_mcp_request(): bool {
	return function_exists( 'wpagent_companion_oauth_request_resource' )
		&& function_exists( 'wpagent_companion_oauth_mcp_resource' )
		&& wpagent_companion_oauth_request_resource() === wpagent_companion_oauth_mcp_resource();
}

/**
 * Bildet ausschließlich dokumentierte WPForms-Berechtigungsprüfungen auf die
 * drei expliziten wpagently-Rechte ab. Unbekannte Prüfungen bleiben unverändert
 * abgelehnt. Ein Schreibrecht impliziert den zum Bearbeiten nötigen Formularblick.
 *
 * @param mixed        $allowed Ursprüngliches WPForms-Ergebnis.
 * @param string|array $caps    Kurze oder wpforms_-präfixierte WPForms-Rechte.
 * @return mixed
 */
function wpagent_companion_wpforms_current_user_can( $allowed, $caps, int $form_id = 0 ) {
	if ( $allowed || ! wpagent_companion_wpforms_is_mcp_request() ) {
		return $allowed;
	}
	$user = wp_get_current_user();
	if ( ! $user instanceof WP_User || ! $user->exists() || ! wpagent_companion_is_safe_connection_user( $user ) ) {
		return $allowed;
	}

	$requested = is_array( $caps ) ? $caps : array( $caps );
	if ( ! $requested ) {
		return $allowed;
	}
	$groups    = wpagent_companion_wpforms_access_capabilities();
	$mapping   = array(
		'view_forms'              => array( $groups['forms'], $groups['write'] ),
		'view_form_single'        => array( $groups['forms'], $groups['write'] ),
		'view_entries_form_single' => array( $groups['entries'] ),
		'view_entry_single'       => array( $groups['entries'] ),
		'view_entries'            => array( $groups['entries'] ),
		'create_forms'            => array( $groups['write'] ),
		'edit_forms'              => array( $groups['write'] ),
		'edit_form_single'        => array( $groups['write'] ),
	);

	foreach ( $requested as $capability ) {
		if ( ! is_string( $capability ) ) {
			return $allowed;
		}
		$capability = str_starts_with( $capability, 'wpforms_' ) ? substr( $capability, 8 ) : $capability;
		if ( ! isset( $mapping[ $capability ] ) ) {
			return $allowed;
		}
		$has_group = false;
		foreach ( $mapping[ $capability ] as $wpagent_capability ) {
			if ( user_can( $user, $wpagent_capability ) ) {
				$has_group = true;
				break;
			}
		}
		if ( ! $has_group ) {
			return $allowed;
		}
	}

	return true;
}
add_filter( 'wpforms_current_user_can', 'wpagent_companion_wpforms_current_user_can', 20, 3 );

/**
 * Maskiert IP-Adressen in nativen WPForms-Eintragsantworten für den begrenzten
 * Agent-Nutzer. Eine bereits aktivierte strengere Provider-Einstellung bleibt
 * selbstverständlich aktiv.
 *
 * @param mixed $mask Ursprünglicher WPForms-Filterwert.
 */
function wpagent_companion_wpforms_mask_entry_ip( $mask ): bool {
	if ( $mask ) {
		return true;
	}
	if ( ! wpagent_companion_wpforms_is_mcp_request() ) {
		return false;
	}
	$user = wp_get_current_user();
	if ( ! $user instanceof WP_User || ! $user->exists() || ! wpagent_companion_is_safe_connection_user( $user ) ) {
		return false;
	}
	$capability = wpagent_companion_wpforms_access_capabilities()['entries'];
	return user_can( $user, $capability );
}
add_filter( 'wpforms_abilities_mask_ip_address', 'wpagent_companion_wpforms_mask_entry_ip' );

/**
 * Offiziell dokumentierte native WPForms-Abilities.
 *
 * describe-editing-schema ist lesend, wird von WPForms aber zusammen mit den
 * Schreibwerkzeugen veröffentlicht. Es gehört deshalb in die Schreibgruppe.
 * Neue, noch unbekannte WPForms-Abilities bleiben weiterhin einzeln über den
 * generischen Fremd-Ability-Katalog steuerbar.
 *
 * @return array{forms:string[],entries:string[],write:string[]}
 */
function wpagent_companion_wpforms_ability_groups(): array {
	return array(
		'forms'   => array(
			'wpforms/list-forms',
			'wpforms/get-form',
			'wpforms/get-form-stats',
		),
		'entries' => array(
			'wpforms/get-entry-summaries',
			'wpforms/get-entry',
			'wpforms/search-entries',
		),
		'write'   => array(
			'wpforms/describe-editing-schema',
			'wpforms/create-form',
			'wpforms/add-field',
			'wpforms/update-field',
			'wpforms/update-form-settings',
		),
	);
}

/** @return string[] */
function wpagent_companion_wpforms_known_ability_names(): array {
	$groups = wpagent_companion_wpforms_ability_groups();
	return array_merge( $groups['forms'], $groups['entries'], $groups['write'] );
}

/**
 * Prüft die vom Provider selbst gesetzte MCP-Freigabe streng auf true.
 *
 * @param mixed $ability WordPress-Ability-Objekt.
 */
function wpagent_companion_ability_is_mcp_public( $ability ): bool {
	if ( ! is_object( $ability ) || ! method_exists( $ability, 'get_meta' ) ) {
		return false;
	}
	$meta = $ability->get_meta();
	return is_array( $meta )
		&& isset( $meta['mcp'] )
		&& is_array( $meta['mcp'] )
		&& true === ( $meta['mcp']['public'] ?? null );
}

/**
 * Nur aktuell von WPForms selbst veröffentlichte, bekannte Abilities.
 *
 * @return string[]
 */
function wpagent_companion_wpforms_public_ability_names(): array {
	$names = array();
	foreach ( wpagent_companion_wpforms_known_ability_names() as $name ) {
		$ability = wpagent_companion_get_registered_ability( $name );
		if ( wpagent_companion_ability_is_mcp_public( $ability ) ) {
			$names[] = $name;
		}
	}
	return $names;
}

/**
 * Diagnose für das Kontrollzentrum, ohne WPForms-Optionen oder Filter zu
 * überschreiben. Die öffentliche Metadatenfreigabe ist die maßgebliche Quelle.
 *
 * @return array{
 *   detected:bool,
 *   registered_forms:string[],
 *   available_forms:string[],
 *   registered_entries:string[],
 *   available_entries:string[],
 *   registered_write:string[],
 *   available_write:string[],
 *   write_access_enabled:bool
 * }
 */
function wpagent_companion_wpforms_integration_status(): array {
	$groups     = wpagent_companion_wpforms_ability_groups();
	$registered = array( 'forms' => array(), 'entries' => array(), 'write' => array() );
	$available  = array( 'forms' => array(), 'entries' => array(), 'write' => array() );
	$detected   = false;

	if ( function_exists( 'wp_get_abilities' ) ) {
		foreach ( array_keys( wp_get_abilities() ) as $name ) {
			if ( is_string( $name ) && str_starts_with( $name, 'wpforms/' ) ) {
				$detected = true;
				break;
			}
		}
	}

	foreach ( $groups as $group => $names ) {
		foreach ( $names as $name ) {
			$ability = wpagent_companion_get_registered_ability( $name );
			if ( ! $ability ) {
				continue;
			}
			$registered[ $group ][] = $name;
			if ( wpagent_companion_ability_is_mcp_public( $ability ) ) {
				$available[ $group ][] = $name;
			}
		}
	}
	$detected = $detected || ! empty( $registered['forms'] ) || ! empty( $registered['entries'] ) || ! empty( $registered['write'] );

	return array(
		'detected'             => $detected,
		'registered_forms'     => $registered['forms'],
		'available_forms'      => $available['forms'],
		'registered_entries'   => $registered['entries'],
		'available_entries'    => $available['entries'],
		'registered_write'     => $registered['write'],
		'available_write'      => $available['write'],
		'write_access_enabled' => ! empty( $registered['write'] ) && count( $registered['write'] ) === count( $available['write'] ),
	);
}

/**
 * Baut die gespeicherte Fremd-Ability-Auswahl aus generischen Einzelwerten und
 * den drei WPForms-Gruppenschaltern. Provider-versteckte Abilities werden
 * selbst bei manipulierten POST-Werten nie aufgenommen.
 *
 * @param mixed[] $submitted Generisch ausgewählte externe Abilities.
 * @return string[]
 */
function wpagent_companion_merge_wpforms_trust( array $submitted, bool $enable_forms, bool $enable_entries, bool $enable_write ): array {
	$catalog = wpagent_companion_external_ability_catalog();
	$known   = array_flip( wpagent_companion_wpforms_known_ability_names() );
	$trusted = array();

	foreach ( $submitted as $name ) {
		if ( ! is_string( $name ) ) {
			continue;
		}
		$name = sanitize_text_field( $name );
		$is_wpforms = str_starts_with( $name, 'wpforms/' );
		$is_public  = ! $is_wpforms || wpagent_companion_ability_is_mcp_public( wpagent_companion_get_registered_ability( $name ) );
		if ( isset( $catalog[ $name ] ) && ! isset( $known[ $name ] ) && $is_public ) {
			$trusted[] = $name;
		}
	}

	$groups = wpagent_companion_wpforms_ability_groups();
	$public = array_flip( wpagent_companion_wpforms_public_ability_names() );
	foreach ( array( 'forms' => $enable_forms, 'entries' => $enable_entries, 'write' => $enable_write ) as $group => $enabled ) {
		if ( ! $enabled ) {
			continue;
		}
		foreach ( $groups[ $group ] as $name ) {
			if ( isset( $catalog[ $name ], $public[ $name ] ) ) {
				$trusted[] = $name;
			}
		}
	}

	$trusted = array_values( array_unique( $trusted ) );
	sort( $trusted, SORT_STRING );
	return $trusted;
}
