<?php
/** Shared, native Gravity Forms GFAPI adapter helpers. */

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

define( 'WPAGENT_COMPANION_OPT_GRAVITY_GRANTS', 'wp_agent_companion_gravity_capability_grants' );
define( 'WPAGENT_COMPANION_OPT_GRAVITY_ENTRY_GRANTS', 'wp_agent_companion_gravity_entry_capability_grants' );
define( 'WPAGENT_GRAVITY_FORM_LOCK_PREFIX', 'wp_agent_gravity_form_lock_' );
define( 'WPAGENT_GRAVITY_FORM_LOCK_TTL', 120 );

/** @return string[] */
function wpagent_gravity_required_capabilities(): array {
	return array(
		'gravityforms_edit_forms',
		'gravityforms_create_form',
		'gravityforms_delete_forms',
	);
}

/**
 * Only capabilities explicitly added by WPAgently are recorded. This lets the
 * administrator remove the integration again without touching role grants or
 * capabilities that existed before WPAgently was enabled for the user.
 *
 * @return array<int,string[]>
 */
function wpagent_gravity_capability_grants(): array {
	$stored = get_option( WPAGENT_COMPANION_OPT_GRAVITY_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_gravity_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_gravity_store_capability_grants( array $grants ): bool {
	if ( $grants ) {
		update_option( WPAGENT_COMPANION_OPT_GRAVITY_GRANTS, $grants, false );
	} else {
		delete_option( WPAGENT_COMPANION_OPT_GRAVITY_GRANTS );
	}
	return wpagent_gravity_capability_grants() === $grants;
}

/** @return array{enabled:bool,capabilities:string[],tracked:string[]}|WP_Error */
function wpagent_gravity_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_gravity_unsafe_user', __( 'Wähle einen Redakteur ohne Administrator- oder Super-Administratorrechte.', 'wp-agent-companion' ), array( 'status' => 400 ) );
	}
	$capabilities = array_values(
		array_filter(
			wpagent_gravity_required_capabilities(),
			static fn( string $capability ): bool => user_can( $user, $capability )
		)
	);
	$grants = wpagent_gravity_capability_grants();
	return array(
		'enabled'      => count( $capabilities ) === count( wpagent_gravity_required_capabilities() ),
		'capabilities' => $capabilities,
		'tracked'      => $grants[ $user_id ] ?? array(),
	);
}

/** @return array{enabled:bool,capabilities:string[],tracked:string[]}|WP_Error */
function wpagent_gravity_set_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_gravity_unsafe_user', __( 'Wähle einen Redakteur ohne Administrator- oder Super-Administratorrechte.', 'wp-agent-companion' ), array( 'status' => 400 ) );
	}
	if ( $enabled && ! wpagent_gravity_available() ) {
		return wpagent_gravity_missing_error();
	}

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

	try {
		$grants         = wpagent_gravity_capability_grants();
		$original_grants = $grants;
		$tracked        = $grants[ $user_id ] ?? array();
		if ( $enabled ) {
			$newly_added = array();
			foreach ( wpagent_gravity_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;
			} else {
				unset( $grants[ $user_id ] );
			}
			if ( ! wpagent_gravity_store_capability_grants( $grants ) ) {
				foreach ( $newly_added as $capability ) {
					$user->remove_cap( $capability );
				}
				wpagent_gravity_store_capability_grants( $original_grants );
				clean_user_cache( $user_id );
				return new WP_Error( 'wpagent_gravity_grant_store_failed', __( 'Die Gravity-Forms-Rechte 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_gravity_grant_remove_failed', __( 'Die von WPAgently ergänzten Gravity-Forms-Rechte konnten nicht vollständig entfernt werden.', 'wp-agent-companion' ), array( 'status' => 500 ) );
			}
			unset( $grants[ $user_id ] );
			if ( ! wpagent_gravity_store_capability_grants( $grants ) ) {
				foreach ( $tracked as $capability ) {
					$user->add_cap( $capability );
				}
				clean_user_cache( $user_id );
				return new WP_Error( 'wpagent_gravity_grant_store_failed', __( 'Die Gravity-Forms-Rechte konnten nicht sicher gespeichert werden.', 'wp-agent-companion' ), array( 'status' => 500 ) );
			}
		}
		clean_user_cache( $user_id );
		$access = wpagent_gravity_user_access( $user_id );
		if ( $enabled && ( is_wp_error( $access ) || ! $access['enabled'] ) ) {
			foreach ( $newly_added as $capability ) {
				$user->remove_cap( $capability );
			}
			wpagent_gravity_store_capability_grants( $original_grants );
			clean_user_cache( $user_id );
			return is_wp_error( $access ) ? $access : new WP_Error( 'wpagent_gravity_grant_failed', __( 'Die Gravity-Forms-Rechte konnten nicht vollständig aktiviert werden.', 'wp-agent-companion' ), array( 'status' => 500 ) );
		}
		return $access;
	} finally {
		wpagent_companion_release_connection_lock( $lock );
	}
}

/** @return string[] */
function wpagent_gravity_entry_required_capabilities(): array {
	return array(
		'gravityforms_view_entries',
		'gravityforms_edit_entries',
		'gravityforms_delete_entries',
	);
}

/** @return array<int,string[]> */
function wpagent_gravity_entry_capability_grants(): array {
	$stored = get_option( WPAGENT_COMPANION_OPT_GRAVITY_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_gravity_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_gravity_store_entry_capability_grants( array $grants ): bool {
	if ( $grants ) {
		update_option( WPAGENT_COMPANION_OPT_GRAVITY_ENTRY_GRANTS, $grants, false );
	} else {
		delete_option( WPAGENT_COMPANION_OPT_GRAVITY_ENTRY_GRANTS );
	}
	return wpagent_gravity_entry_capability_grants() === $grants;
}

/** @return array{enabled:bool,capabilities:string[],tracked:string[]}|WP_Error */
function wpagent_gravity_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_gravity_unsafe_user', __( 'Wähle einen Redakteur ohne Administrator- oder Super-Administratorrechte.', 'wp-agent-companion' ), array( 'status' => 400 ) );
	}
	$capabilities = array_values(
		array_filter(
			wpagent_gravity_entry_required_capabilities(),
			static fn( string $capability ): bool => user_can( $user, $capability )
		)
	);
	$grants = wpagent_gravity_entry_capability_grants();
	return array(
		'enabled'      => count( $capabilities ) === count( wpagent_gravity_entry_required_capabilities() ),
		'capabilities' => $capabilities,
		'tracked'      => $grants[ $user_id ] ?? array(),
	);
}

/**
 * Entry data can contain personal and payment information. These capabilities
 * are deliberately managed separately and are never granted automatically
 * together with the form-definition capabilities.
 *
 * @return array{enabled:bool,capabilities:string[],tracked:string[]}|WP_Error
 */
function wpagent_gravity_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_gravity_unsafe_user', __( 'Wähle einen Redakteur ohne Administrator- oder Super-Administratorrechte.', 'wp-agent-companion' ), array( 'status' => 400 ) );
	}
	if ( $enabled && ! wpagent_gravity_available() ) {
		return wpagent_gravity_missing_error();
	}

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

	try {
		$grants          = wpagent_gravity_entry_capability_grants();
		$original_grants = $grants;
		$tracked         = $grants[ $user_id ] ?? array();
		if ( $enabled ) {
			$newly_added = array();
			foreach ( wpagent_gravity_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;
			} else {
				unset( $grants[ $user_id ] );
			}
			if ( ! wpagent_gravity_store_entry_capability_grants( $grants ) ) {
				foreach ( $newly_added as $capability ) {
					$user->remove_cap( $capability );
				}
				wpagent_gravity_store_entry_capability_grants( $original_grants );
				clean_user_cache( $user_id );
				return new WP_Error( 'wpagent_gravity_entry_grant_store_failed', __( 'Die Gravity-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_gravity_entry_grant_remove_failed', __( 'Die von WPAgently ergänzten Gravity-Forms-Eintragsrechte konnten nicht vollständig entfernt werden.', 'wp-agent-companion' ), array( 'status' => 500 ) );
			}
			unset( $grants[ $user_id ] );
			if ( ! wpagent_gravity_store_entry_capability_grants( $grants ) ) {
				foreach ( $tracked as $capability ) {
					$user->add_cap( $capability );
				}
				clean_user_cache( $user_id );
				return new WP_Error( 'wpagent_gravity_entry_grant_store_failed', __( 'Die Gravity-Forms-Eintragsrechte konnten nicht sicher gespeichert werden.', 'wp-agent-companion' ), array( 'status' => 500 ) );
			}
		}
		clean_user_cache( $user_id );
		$access = wpagent_gravity_entry_user_access( $user_id );
		if ( $enabled && ( is_wp_error( $access ) || ! $access['enabled'] ) ) {
			foreach ( $newly_added as $capability ) {
				$user->remove_cap( $capability );
			}
			wpagent_gravity_store_entry_capability_grants( $original_grants );
			clean_user_cache( $user_id );
			return is_wp_error( $access ) ? $access : new WP_Error( 'wpagent_gravity_entry_grant_failed', __( 'Die Gravity-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_gravity_available(): bool {
	return class_exists( 'GFAPI' )
		&& is_callable( array( 'GFAPI', 'get_form' ) )
		&& is_callable( array( 'GFAPI', 'get_forms' ) );
}

function wpagent_gravity_missing_error(): WP_Error {
	return new WP_Error(
		'wpagent_gravity_forms_missing',
		__( 'Gravity Forms ist nicht aktiv oder seine öffentliche GFAPI ist nicht verfügbar.', 'wp-agent-companion' ),
		array( 'status' => 400 )
	);
}

/** @param mixed $input */
function wpagent_perm_gravity_read( $input = null ): bool {
	return current_user_can( 'gravityforms_edit_forms' );
}

/** @param mixed $input */
function wpagent_perm_gravity_create( $input = null ): bool {
	return current_user_can( 'gravityforms_create_form' );
}

/** @param mixed $input */
function wpagent_perm_gravity_edit( $input = null ): bool {
	return current_user_can( 'gravityforms_edit_forms' );
}

/** @param mixed $input */
function wpagent_perm_gravity_duplicate( $input = null ): bool {
	return current_user_can( 'gravityforms_create_form' ) && current_user_can( 'gravityforms_edit_forms' );
}

/** @param mixed $input */
function wpagent_perm_gravity_delete( $input = null ): bool {
	return current_user_can( 'gravityforms_delete_forms' );
}

/** @param mixed $value @return mixed */
function wpagent_gravity_json_value( $value ) {
	$encoded = wp_json_encode( $value );
	if ( false === $encoded ) {
		return null;
	}
	return json_decode( $encoded, true );
}

/** @param mixed $value @return mixed */
function wpagent_gravity_canonicalize_value( $value ) {
	if ( ! is_array( $value ) ) {
		return $value;
	}
	if ( array_is_list( $value ) ) {
		return array_map( 'wpagent_gravity_canonicalize_value', $value );
	}
	ksort( $value, SORT_STRING );
	foreach ( $value as $key => $item ) {
		$value[ $key ] = wpagent_gravity_canonicalize_value( $item );
	}
	return $value;
}

function wpagent_gravity_sensitive_state_key( string $key ): bool {
	return 1 === preg_match( '/(?:api[_-]?key|authorization|credential|password|secret|token|private[_-]?key)/i', $key );
}

/** @param mixed $value @return mixed */
function wpagent_gravity_redacted_state_value_normalized( $value, string $key = '' ) {
	if ( '' !== $key && wpagent_gravity_sensitive_state_key( $key ) ) {
		return '__wpagently_redacted__';
	}
	if ( ! is_array( $value ) ) {
		return $value;
	}
	foreach ( $value as $child_key => $child_value ) {
		$value[ $child_key ] = wpagent_gravity_redacted_state_value_normalized( $child_value, (string) $child_key );
	}
	return $value;
}

/**
 * Hashes the complete provider state, but replaces sensitive values before
 * serialization. This detects all non-sensitive configuration drift without
 * making credentials observable through a hash oracle or response payload.
 *
 * @param array<string,mixed> $form
 */
function wpagent_gravity_form_state_hash( array $form ): string {
	$raw     = wpagent_gravity_json_value( $form );
	$state   = wpagent_gravity_canonicalize_value( wpagent_gravity_redacted_state_value_normalized( $raw ) );
	$encoded = wp_json_encode( $state, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE );
	return hash( 'sha256', false === $encoded ? serialize( $state ) : $encoded );
}

/** @return string|WP_Error */
function wpagent_gravity_acquire_form_lock( int $form_id ) {
	if ( $form_id <= 0 ) {
		return new WP_Error( 'wpagent_bad_form_id', __( 'form_id muss eine positive Ganzzahl sein.', 'wp-agent-companion' ), array( 'status' => 400 ) );
	}
	$token = wpagent_companion_acquire_option_lock( WPAGENT_GRAVITY_FORM_LOCK_PREFIX . $form_id, WPAGENT_GRAVITY_FORM_LOCK_TTL );
	if ( is_string( $token ) ) {
		return $token;
	}
	return new WP_Error( 'wpagent_gravity_form_locked', __( 'Dieses Gravity-Forms-Formular wird gerade durch einen anderen Vorgang geändert.', 'wp-agent-companion' ), array( 'status' => 409 ) );
}

function wpagent_gravity_release_form_lock( int $form_id, string $token ): void {
	wpagent_companion_release_option_lock( WPAGENT_GRAVITY_FORM_LOCK_PREFIX . $form_id, $token );
}

function wpagent_gravity_form_lock_is_owned( int $form_id, string $token ): bool {
	return $form_id > 0
		&& wpagent_companion_option_lock_is_owned( WPAGENT_GRAVITY_FORM_LOCK_PREFIX . $form_id, $token );
}

function wpagent_gravity_refresh_form_lock( int $form_id, string $token ): bool {
	return $form_id > 0
		&& wpagent_companion_refresh_option_lock( WPAGENT_GRAVITY_FORM_LOCK_PREFIX . $form_id, $token, WPAGENT_GRAVITY_FORM_LOCK_TTL );
}

/** @return string[] */
function wpagent_gravity_setting_keys(): array {
	return array(
		'labelPlacement', 'descriptionPlacement', 'subLabelPlacement', 'validationSummary',
		'requiredIndicator', 'customRequiredIndicator', 'cssClass', 'enableAnimation', 'markupVersion',
	);
}

/** @param array<string,mixed> $form @return array<string,mixed>|WP_Error */
function wpagent_gravity_form_payload( array $form ) {
	return wpagent_gravity_public_form_payload( $form );
}

/** @param mixed $input @return array<string,mixed>|WP_Error */
function wpagent_gravity_get_authorized_form( $input, string $capability ) {
	if ( ! wpagent_gravity_available() ) {
		return wpagent_gravity_missing_error();
	}
	$input   = is_array( $input ) ? $input : array();
	$form_id = absint( $input['form_id'] ?? 0 );
	if ( $form_id <= 0 ) {
		return new WP_Error( 'wpagent_bad_form_id', __( 'form_id muss eine positive Ganzzahl sein.', 'wp-agent-companion' ), array( 'status' => 400 ) );
	}
	if ( ! current_user_can( $capability ) ) {
		return new WP_Error( 'wpagent_forbidden', __( 'Keine Berechtigung für dieses Gravity-Forms-Formular.', 'wp-agent-companion' ), array( 'status' => 403 ) );
	}
	$form = GFAPI::get_form( $form_id );
	if ( ! is_array( $form ) ) {
		return new WP_Error( 'wpagent_gravity_form_not_found', __( 'Gravity-Forms-Formular nicht gefunden.', 'wp-agent-companion' ), array( 'status' => 404 ) );
	}
	return $form;
}

/** @param mixed $expected @param mixed $actual */
function wpagent_gravity_subset_matches( $expected, $actual ): bool {
	$expected = wpagent_gravity_json_value( $expected );
	$actual   = wpagent_gravity_json_value( $actual );
	if ( is_array( $expected ) ) {
		if ( ! is_array( $actual ) ) {
			return false;
		}
		foreach ( $expected as $key => $value ) {
			if ( ! array_key_exists( $key, $actual ) || ! wpagent_gravity_subset_matches( $value, $actual[ $key ] ) ) {
				return false;
			}
		}
		return true;
	}
	if ( is_bool( $expected ) ) {
		return (bool) $actual === $expected;
	}
	return (string) $actual === (string) $expected;
}

/**
 * Verifies complete top-level collections while still allowing Gravity Forms
 * to add documented default properties inside individual objects.
 *
 * @param mixed $expected
 * @param mixed $actual
 */
function wpagent_gravity_collection_matches( $expected, $actual ): bool {
	$expected = wpagent_gravity_json_value( $expected );
	$actual   = wpagent_gravity_json_value( $actual );
	if ( ! is_array( $expected ) || ! is_array( $actual ) || count( $expected ) !== count( $actual ) ) {
		return false;
	}
	if ( array_is_list( $expected ) !== array_is_list( $actual ) ) {
		return false;
	}
	if ( ! array_is_list( $expected ) ) {
		$expected_keys = array_map( 'strval', array_keys( $expected ) );
		$actual_keys   = array_map( 'strval', array_keys( $actual ) );
		sort( $expected_keys, SORT_STRING );
		sort( $actual_keys, SORT_STRING );
		if ( $expected_keys !== $actual_keys ) {
			return false;
		}
	}
	foreach ( $expected as $key => $value ) {
		if ( ! array_key_exists( $key, $actual ) || ! wpagent_gravity_subset_matches( $value, $actual[ $key ] ) ) {
			return false;
		}
	}
	return true;
}

/** @return array<string,mixed> */
function wpagent_gravity_settings_schema(): array {
	return array(
		'type'                 => 'object',
		'additionalProperties' => false,
		'properties'           => array(
			'labelPlacement'          => array( 'type' => 'string', 'enum' => array( 'top_label', 'left_label', 'right_label' ) ),
			'descriptionPlacement'    => array( 'type' => 'string', 'enum' => array( 'above', 'below' ) ),
			'subLabelPlacement'       => array( 'type' => 'string', 'enum' => array( 'above', 'below' ) ),
			'validationSummary'       => array( 'type' => 'boolean' ),
			'requiredIndicator'       => array( 'type' => 'string', 'enum' => array( 'text', 'asterisk', 'custom' ) ),
			'customRequiredIndicator' => array( 'type' => 'string', 'maxLength' => 100 ),
			'cssClass'                => array( 'type' => 'string', 'maxLength' => 500 ),
			'enableAnimation'         => array( 'type' => 'boolean' ),
			'markupVersion'           => array( 'type' => 'integer', 'enum' => array( 1, 2 ) ),
		),
	);
}

/** @return array<string,mixed> */
function wpagent_gravity_write_properties_schema(): array {
	return array(
		'title'         => array( 'type' => 'string', 'minLength' => 1, 'maxLength' => 255 ),
		'description'   => array( 'type' => 'string', 'maxLength' => 10000 ),
		'submit_label'  => array( 'type' => 'string', 'minLength' => 1, 'maxLength' => 500 ),
		'fields'        => array( 'type' => 'array', 'maxItems' => 200, 'items' => array( 'type' => 'object', 'maxProperties' => 100 ) ),
		'notifications' => array( 'type' => 'object', 'maxProperties' => 100, 'additionalProperties' => array( 'type' => 'object', 'maxProperties' => 100 ) ),
		'confirmations' => array( 'type' => 'object', 'maxProperties' => 100, 'additionalProperties' => array( 'type' => 'object', 'maxProperties' => 100 ) ),
		'settings'      => wpagent_gravity_settings_schema(),
	);
}
