<?php
/** Shared helpers for bounded Fluent Forms entry access. */

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

define( 'WPAGENT_COMPANION_OPT_FLUENT_FORMS_ENTRY_GRANTS', 'wp_agent_companion_fluent_forms_entry_capability_grants' );
define( 'WPAGENT_FLUENT_ENTRY_LOCK_PREFIX', 'wp_agent_fluent_entry_lock_' );
define( 'WPAGENT_FLUENT_ENTRY_LOCK_TTL', 60 );

/** @return string[] */
function wpagent_fluent_forms_entry_required_capabilities(): array {
	return array( 'fluentform_entries_viewer', 'fluentform_manage_entries' );
}

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

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

/** @return array{enabled:bool,capabilities:string[],tracked:string[]}|WP_Error */
function wpagent_fluent_forms_entry_user_access( int $user_id ) {
	$user = get_user_by( 'id', $user_id );
	if ( ! $user instanceof WP_User || ! wpagent_companion_is_safe_connection_user( $user ) ) {
		return new WP_Error( 'wpagent_fluent_entries_unsafe_user', __( 'Wähle einen Redakteur ohne Administrator- oder Super-Administratorrechte.', 'wp-agent-companion' ), array( 'status' => 400 ) );
	}
	$capabilities = array_values(
		array_filter(
			wpagent_fluent_forms_entry_required_capabilities(),
			static fn( string $capability ): bool => user_can( $user, $capability )
		)
	);
	$grants = wpagent_fluent_forms_entry_capability_grants();
	return array(
		'enabled'      => count( $capabilities ) === count( wpagent_fluent_forms_entry_required_capabilities() ),
		'capabilities' => $capabilities,
		'tracked'      => $grants[ $user_id ] ?? array(),
	);
}

/** @return array{enabled:bool,capabilities:string[],tracked:string[]}|WP_Error */
function wpagent_fluent_forms_set_entry_user_access( int $user_id, bool $enabled ) {
	$user = get_user_by( 'id', $user_id );
	if ( ! $user instanceof WP_User || ! wpagent_companion_is_safe_connection_user( $user ) ) {
		return new WP_Error( 'wpagent_fluent_entries_unsafe_user', __( 'Wähle einen Redakteur ohne Administrator- oder Super-Administratorrechte.', 'wp-agent-companion' ), array( 'status' => 400 ) );
	}
	if ( $enabled && ! wpagent_fluent_entries_available() ) {
		return wpagent_fluent_entries_missing_error();
	}
	$lock = wpagent_companion_acquire_connection_lock();
	if ( is_wp_error( $lock ) ) {
		return $lock;
	}

	try {
		$grants          = wpagent_fluent_forms_entry_capability_grants();
		$original_grants = $grants;
		$tracked         = $grants[ $user_id ] ?? array();
		$newly_added     = array();
		if ( $enabled ) {
			foreach ( wpagent_fluent_forms_entry_required_capabilities() as $capability ) {
				if ( in_array( $capability, $tracked, true ) ) {
					$user->add_cap( $capability );
					continue;
				}
				if ( ! user_can( $user, $capability ) ) {
					$user->add_cap( $capability );
					$tracked[]     = $capability;
					$newly_added[] = $capability;
				}
			}
			$tracked = array_values( array_unique( $tracked ) );
			if ( $tracked ) {
				$grants[ $user_id ] = $tracked;
			}
			if ( ! wpagent_fluent_forms_store_entry_capability_grants( $grants ) ) {
				foreach ( $newly_added as $capability ) {
					$user->remove_cap( $capability );
				}
				wpagent_fluent_forms_store_entry_capability_grants( $original_grants );
				clean_user_cache( $user_id );
				return new WP_Error( 'wpagent_fluent_entry_grant_store_failed', __( 'Die Fluent-Forms-Eintragsrechte konnten nicht sicher gespeichert werden.', 'wp-agent-companion' ), array( 'status' => 500 ) );
			}
		} else {
			foreach ( $tracked as $capability ) {
				$user->remove_cap( $capability );
			}
			clean_user_cache( $user_id );
			$fresh_user = get_user_by( 'id', $user_id );
			$remaining  = $fresh_user instanceof WP_User
				? array_values( array_intersect( $tracked, array_keys( array_filter( (array) $fresh_user->caps ) ) ) )
				: $tracked;
			if ( $remaining ) {
				foreach ( $tracked as $capability ) {
					$user->add_cap( $capability );
				}
				clean_user_cache( $user_id );
				return new WP_Error( 'wpagent_fluent_entry_grant_remove_failed', __( 'Die von WPAgently ergänzten Fluent-Forms-Eintragsrechte konnten nicht vollständig entfernt werden.', 'wp-agent-companion' ), array( 'status' => 500 ) );
			}
			unset( $grants[ $user_id ] );
			if ( ! wpagent_fluent_forms_store_entry_capability_grants( $grants ) ) {
				foreach ( $tracked as $capability ) {
					$user->add_cap( $capability );
				}
				clean_user_cache( $user_id );
				return new WP_Error( 'wpagent_fluent_entry_grant_store_failed', __( 'Die Fluent-Forms-Eintragsrechte konnten nicht sicher gespeichert werden.', 'wp-agent-companion' ), array( 'status' => 500 ) );
			}
		}
		clean_user_cache( $user_id );
		$access = wpagent_fluent_forms_entry_user_access( $user_id );
		if ( $enabled && ( is_wp_error( $access ) || ! $access['enabled'] ) ) {
			foreach ( $newly_added as $capability ) {
				$user->remove_cap( $capability );
			}
			wpagent_fluent_forms_store_entry_capability_grants( $original_grants );
			clean_user_cache( $user_id );
			return is_wp_error( $access ) ? $access : new WP_Error( 'wpagent_fluent_entry_grant_failed', __( 'Die Fluent-Forms-Eintragsrechte konnten nicht vollständig aktiviert werden.', 'wp-agent-companion' ), array( 'status' => 500 ) );
		}
		return $access;
	} finally {
		wpagent_companion_release_connection_lock( $lock );
	}
}

function wpagent_fluent_entries_available(): bool {
	return wpagent_fluent_forms_available()
		&& class_exists( 'FluentForm\\App\\Api\\Entry' )
		&& class_exists( 'FluentForm\\App\\Models\\Submission' )
		&& class_exists( 'FluentForm\\App\\Services\\Submission\\SubmissionService' )
		&& class_exists( 'FluentForm\\App\\Helpers\\Helper' );
}

function wpagent_fluent_entries_missing_error(): WP_Error {
	return new WP_Error( 'wpagent_fluent_entries_missing', __( 'Fluent Forms ist nicht aktiv oder seine native Eintrags-API ist nicht verfügbar.', 'wp-agent-companion' ), array( 'status' => 400 ) );
}

/** @param mixed $input */
function wpagent_perm_fluent_entries_read( $input = null ): bool {
	return wpagent_fluent_forms_has_permission( 'fluentform_entries_viewer' );
}

/** @param mixed $input */
function wpagent_perm_fluent_entries_manage( $input = null ): bool {
	return wpagent_fluent_forms_has_permission( 'fluentform_manage_entries' );
}

/** @param mixed $entry @return array<string,mixed> */
function wpagent_fluent_entry_model_array( $entry ): array {
	return wpagent_fluent_forms_model_array( $entry );
}

/** @param mixed $entry */
function wpagent_fluent_entry_version( $entry ): string {
	$data                 = wpagent_fluent_entry_model_array( $entry );
	$response             = $data['response'] ?? '';
	$response_fingerprint = is_string( $response )
		? hash( 'sha256', $response )
		: hash( 'sha256', (string) wp_json_encode( $response ) );
	return hash(
		'sha256',
		(string) wp_json_encode(
			array(
				'id'           => absint( $data['id'] ?? 0 ),
				'form_id'      => absint( $data['form_id'] ?? 0 ),
				'status'       => (string) ( $data['status'] ?? '' ),
				'is_favourite' => ! empty( $data['is_favourite'] ),
				'response'     => $response_fingerprint,
				'updated_at'   => (string) ( $data['updated_at'] ?? '' ),
			)
		)
	);
}

/**
 * Sanitize a response value while enforcing one shared traversal budget.
 *
 * @param mixed                                      $value  Value to sanitize.
 * @param int                                        $depth  Current nesting depth.
 * @param array{nodes:int,exceeded:bool}|null        $budget Shared traversal state.
 * @return mixed
 */
function wpagent_fluent_entry_safe_value( $value, int $depth = 0, ?array &$budget = null ) {
	if ( null === $budget ) {
		$budget = array( 'nodes' => 2000, 'exceeded' => false );
	}
	if ( $budget['nodes'] <= 0 ) {
		$budget['exceeded'] = true;
		return null;
	}
	--$budget['nodes'];
	if ( $depth > 6 ) {
		$budget['exceeded'] = true;
		return null;
	}
	if ( null === $value || is_bool( $value ) || is_int( $value ) || is_float( $value ) ) {
		return $value;
	}
	if ( is_string( $value ) ) {
		return wp_html_excerpt( wp_strip_all_tags( $value ), 16384, '' );
	}
	if ( ! is_array( $value ) ) {
		return null;
	}
	if ( count( $value ) > 500 ) {
		$budget['exceeded'] = true;
		return null;
	}
	$result = array();
	foreach ( $value as $key => $item ) {
		$safe_key            = is_int( $key ) ? $key : wp_html_excerpt( sanitize_text_field( (string) $key ), 255, '' );
		$result[ $safe_key ] = wpagent_fluent_entry_safe_value( $item, $depth + 1, $budget );
		if ( $budget['exceeded'] ) {
			return null;
		}
	}
	return $result;
}

/** @param mixed $entry @return array<string,mixed>|WP_Error */
function wpagent_fluent_entry_payload( $entry, bool $include_values ) {
	$data = wpagent_fluent_entry_model_array( $entry );
	if ( ! $data || absint( $data['id'] ?? 0 ) <= 0 ) {
		return new WP_Error( 'wpagent_fluent_entry_not_found', __( 'Fluent-Forms-Eintrag nicht gefunden.', 'wp-agent-companion' ), array( 'status' => 404 ) );
	}
	$payload = array(
		'id'           => absint( $data['id'] ?? 0 ),
		'form_id'      => absint( $data['form_id'] ?? 0 ),
		'serial_number' => absint( $data['serial_number'] ?? 0 ),
		'status'       => sanitize_key( (string) ( $data['status'] ?? '' ) ),
		'is_favourite' => ! empty( $data['is_favourite'] ),
		'created_at'   => sanitize_text_field( (string) ( $data['created_at'] ?? '' ) ),
		'updated_at'   => sanitize_text_field( (string) ( $data['updated_at'] ?? '' ) ),
		'version'      => wpagent_fluent_entry_version( $data ),
	);
	if ( $include_values ) {
		$values = $data['response'] ?? array();
		if ( is_string( $values ) ) {
			if ( strlen( $values ) > 524288 ) {
				return new WP_Error( 'wpagent_fluent_entry_too_large', __( 'Die Eintragswerte überschreiten die sichere Ausgabegrenze von 512 KiB.', 'wp-agent-companion' ), array( 'status' => 413 ) );
			}
			if ( ! str_starts_with( ltrim( $values ), '{' ) ) {
				return new WP_Error( 'wpagent_fluent_entry_invalid_response', __( 'Die gespeicherten Fluent-Forms-Eintragswerte enthalten keine gültigen JSON-Daten.', 'wp-agent-companion' ), array( 'status' => 500 ) );
			}
			$values = json_decode( $values, true, 8 );
			if ( JSON_ERROR_NONE !== json_last_error() || ! is_array( $values ) ) {
				return new WP_Error( 'wpagent_fluent_entry_invalid_response', __( 'Die gespeicherten Fluent-Forms-Eintragswerte enthalten keine gültigen JSON-Daten.', 'wp-agent-companion' ), array( 'status' => 500 ) );
			}
		}
		$budget = null;
		$values = is_array( $values ) ? wpagent_fluent_entry_safe_value( $values, 0, $budget ) : array();
		if ( ( $budget['exceeded'] ?? false ) || strlen( (string) wp_json_encode( $values ) ) > 524288 ) {
			return new WP_Error( 'wpagent_fluent_entry_too_large', __( 'Die Eintragswerte überschreiten die sichere Ausgabegrenze von 512 KiB.', 'wp-agent-companion' ), array( 'status' => 413 ) );
		}
		// The hardened public boundary envelopes this dynamic map as an object.
		// Its canonical encoder deliberately serializes an empty PHP array as
		// `{}` for object documents, while stdClass would bypass that exact
		// map contract and fail closed after a successful provider read.
		$payload['values'] = $values;
	}
	return $payload;
}

/** @return array<string,string>|WP_Error */
function wpagent_fluent_entry_statuses( int $form_id ) {
	try {
		$statuses = \FluentForm\App\Helpers\Helper::getEntryStatuses( $form_id );
	} catch ( Throwable $error ) {
		return new WP_Error( 'wpagent_fluent_entry_statuses_failed', __( 'Die verfügbaren Fluent-Forms-Eintragsstatus konnten nicht gelesen werden.', 'wp-agent-companion' ), array( 'status' => 500 ) );
	}
	$result   = array();
	foreach ( is_array( $statuses ) ? array_slice( $statuses, 0, 50, true ) : array() as $key => $label ) {
		$key = sanitize_key( (string) $key );
		if ( '' !== $key && is_scalar( $label ) ) {
			$result[ $key ] = sanitize_text_field( (string) $label );
		}
	}
	return $result;
}

/** @param mixed $input @return mixed|WP_Error */
function wpagent_fluent_get_authorized_entry( $input, string $capability ) {
	if ( ! wpagent_fluent_entries_available() ) {
		return wpagent_fluent_entries_missing_error();
	}
	$input    = is_array( $input ) ? $input : array();
	$entry_id = absint( $input['entry_id'] ?? 0 );
	if ( $entry_id <= 0 ) {
		return new WP_Error( 'wpagent_bad_entry_id', __( 'entry_id muss eine positive Ganzzahl sein.', 'wp-agent-companion' ), array( 'status' => 400 ) );
	}
	try {
		$entry = \FluentForm\App\Models\Submission::find( $entry_id );
	} catch ( Throwable $error ) {
		return new WP_Error( 'wpagent_fluent_entry_lookup_failed', __( 'Der Fluent-Forms-Eintrag konnte nicht sicher geladen werden.', 'wp-agent-companion' ), array( 'status' => 500 ) );
	}
	if ( ! $entry ) {
		return new WP_Error( 'wpagent_fluent_entry_not_found', __( 'Fluent-Forms-Eintrag nicht gefunden.', 'wp-agent-companion' ), array( 'status' => 404 ) );
	}
	$form_id = absint( wpagent_fluent_entry_model_array( $entry )['form_id'] ?? 0 );
	if ( $form_id <= 0 ) {
		return new WP_Error( 'wpagent_fluent_entry_invalid_form', __( 'Der Fluent-Forms-Eintrag ist keinem gültigen Formular zugeordnet.', 'wp-agent-companion' ), array( 'status' => 500 ) );
	}
	if ( ! wpagent_fluent_forms_has_permission( $capability, $form_id ) ) {
		return new WP_Error( 'wpagent_forbidden', __( 'Keine Berechtigung für die Einträge dieses Fluent-Forms-Formulars.', 'wp-agent-companion' ), array( 'status' => 403 ) );
	}
	return $entry;
}

/** @return string|WP_Error */
function wpagent_fluent_entry_acquire_lock( int $entry_id ) {
	if ( $entry_id <= 0 ) {
		return new WP_Error( 'wpagent_bad_entry_id', __( 'entry_id muss eine positive Ganzzahl sein.', 'wp-agent-companion' ), array( 'status' => 400 ) );
	}
	$token = wpagent_companion_acquire_option_lock( WPAGENT_FLUENT_ENTRY_LOCK_PREFIX . $entry_id, WPAGENT_FLUENT_ENTRY_LOCK_TTL );
	if ( is_string( $token ) ) {
		return $token;
	}
	return new WP_Error( 'wpagent_fluent_entry_locked', __( 'Dieser Fluent-Forms-Eintrag wird gerade durch einen anderen Vorgang geändert.', 'wp-agent-companion' ), array( 'status' => 409 ) );
}

function wpagent_fluent_entry_release_lock( int $entry_id, string $token ): void {
	wpagent_companion_release_option_lock( WPAGENT_FLUENT_ENTRY_LOCK_PREFIX . $entry_id, $token );
}

function wpagent_fluent_entry_lock_is_owned( int $entry_id, string $token ): bool {
	return $entry_id > 0
		&& wpagent_companion_option_lock_is_owned( WPAGENT_FLUENT_ENTRY_LOCK_PREFIX . $entry_id, $token );
}

function wpagent_fluent_entry_refresh_lock( int $entry_id, string $token ): bool {
	return $entry_id > 0
		&& wpagent_companion_refresh_option_lock( WPAGENT_FLUENT_ENTRY_LOCK_PREFIX . $entry_id, $token, WPAGENT_FLUENT_ENTRY_LOCK_TTL );
}

/** @param array<string,mixed> $input @param mixed $entry */
function wpagent_fluent_entry_expected_version_matches( array $input, $entry ): bool {
	$expected = (string) ( $input['expected_version'] ?? '' );
	return 1 === preg_match( '/^[a-f0-9]{64}$/', $expected )
		&& hash_equals( wpagent_fluent_entry_version( $entry ), $expected );
}

/** @return array<string,mixed> */
function wpagent_fluent_entry_output_schema(): array {
	return array(
		'type'                 => 'object',
		'additionalProperties' => false,
		'properties'           => array(
			'id' => array( 'type' => 'integer' ), 'form_id' => array( 'type' => 'integer' ),
			'serial_number' => array( 'type' => 'integer' ), 'status' => array( 'type' => 'string' ),
			'is_favourite' => array( 'type' => 'boolean' ), 'created_at' => array( 'type' => 'string' ),
			'updated_at' => array( 'type' => 'string' ), 'version' => array( 'type' => 'string' ),
			'values' => array( 'type' => 'object' ),
		),
	);
}
