<?php
/**
 * Shared Fluent Forms adapter helpers.
 *
 * Form reads and writes use Fluent Forms' FormService. Permissions go through
 * its own ACL so per-form manager scopes remain effective. Unknown model and
 * form-meta properties are never returned because add-ons may store secrets.
 *
 * @package WPAgentCompanion
 */

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

define( 'WPAGENT_COMPANION_OPT_FLUENT_FORMS_GRANTS', 'wp_agent_companion_fluent_forms_capability_grants' );

/** @return string[] */
function wpagent_fluent_forms_required_capabilities(): array {
	return array( 'fluentform_dashboard_access', 'fluentform_forms_manager' );
}

/** @return array<int,string[]> */
function wpagent_fluent_forms_capability_grants(): array {
	$stored = get_option( WPAGENT_COMPANION_OPT_FLUENT_FORMS_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_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_capability_grants( array $grants ): bool {
	if ( $grants ) {
		update_option( WPAGENT_COMPANION_OPT_FLUENT_FORMS_GRANTS, $grants, false );
	} else {
		delete_option( WPAGENT_COMPANION_OPT_FLUENT_FORMS_GRANTS );
	}
	return wpagent_fluent_forms_capability_grants() === $grants;
}

/** @return array{enabled:bool,capabilities:string[],tracked:string[]}|WP_Error */
function wpagent_fluent_forms_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_forms_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_required_capabilities(),
			static fn( string $capability ): bool => user_can( $user, $capability )
		)
	);
	$grants = wpagent_fluent_forms_capability_grants();
	return array(
		'enabled'      => count( $capabilities ) === count( wpagent_fluent_forms_required_capabilities() ),
		'capabilities' => $capabilities,
		'tracked'      => $grants[ $user_id ] ?? array(),
	);
}

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

	try {
		$grants          = wpagent_fluent_forms_capability_grants();
		$original_grants = $grants;
		$tracked         = $grants[ $user_id ] ?? array();
		$newly_added     = array();
		if ( $enabled ) {
			foreach ( wpagent_fluent_forms_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_fluent_forms_store_capability_grants( $grants ) ) {
				foreach ( $newly_added as $capability ) {
					$user->remove_cap( $capability );
				}
				wpagent_fluent_forms_store_capability_grants( $original_grants );
				clean_user_cache( $user_id );
				return new WP_Error( 'wpagent_fluent_forms_grant_store_failed', __( 'Die Fluent-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_fluent_forms_grant_remove_failed', __( 'Die von WPAgently ergänzten Fluent-Forms-Rechte konnten nicht vollständig entfernt werden.', 'wp-agent-companion' ), array( 'status' => 500 ) );
			}
			unset( $grants[ $user_id ] );
			if ( ! wpagent_fluent_forms_store_capability_grants( $grants ) ) {
				foreach ( $tracked as $capability ) {
					$user->add_cap( $capability );
				}
				clean_user_cache( $user_id );
				return new WP_Error( 'wpagent_fluent_forms_grant_store_failed', __( 'Die Fluent-Forms-Rechte konnten nicht sicher gespeichert werden.', 'wp-agent-companion' ), array( 'status' => 500 ) );
			}
		}
		clean_user_cache( $user_id );
		$access = wpagent_fluent_forms_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_capability_grants( $original_grants );
			clean_user_cache( $user_id );
			return is_wp_error( $access ) ? $access : new WP_Error( 'wpagent_fluent_forms_grant_failed', __( 'Die Fluent-Forms-Rechte konnten nicht vollständig aktiviert werden.', 'wp-agent-companion' ), array( 'status' => 500 ) );
		}
		return $access;
	} finally {
		wpagent_companion_release_connection_lock( $lock );
	}
}

function wpagent_fluent_forms_available(): bool {
	return class_exists( 'FluentForm\\App\\Services\\Form\\FormService' )
		&& class_exists( 'FluentForm\\App\\Modules\\Acl\\Acl' );
}

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

function wpagent_fluent_forms_has_permission( string $permission, int $form_id = 0 ): bool {
	if ( ! wpagent_fluent_forms_available() ) {
		return false;
	}
	try {
		return (bool) \FluentForm\App\Modules\Acl\Acl::hasPermission( $permission, $form_id > 0 ? $form_id : false );
	} catch ( Throwable $error ) {
		return false;
	}
}

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

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

/** @return \FluentForm\App\Services\Form\FormService|WP_Error */
function wpagent_fluent_forms_service() {
	if ( ! wpagent_fluent_forms_available() ) {
		return wpagent_fluent_forms_missing_error();
	}
	try {
		return new \FluentForm\App\Services\Form\FormService();
	} catch ( Throwable $error ) {
		return new WP_Error( 'wpagent_fluent_forms_api_failed', __( 'Die Fluent-Forms-API konnte nicht initialisiert werden.', 'wp-agent-companion' ), array( 'status' => 500 ) );
	}
}

/** @param mixed $form @return array<string,mixed> */
function wpagent_fluent_forms_model_array( $form ): array {
	if ( is_array( $form ) ) {
		return $form;
	}
	if ( is_object( $form ) && is_callable( array( $form, 'toArray' ) ) ) {
		$value = $form->toArray();
		return is_array( $value ) ? $value : array();
	}
	if ( $form instanceof stdClass ) {
		$value = get_object_vars( $form );
		return is_array( $value ) ? $value : array();
	}
	return array();
}

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

/**
 * Binds every provider field returned for a form without exposing raw add-on
 * values in REST output. Fluent Forms does not offer a conditional form write,
 * so this is a preflight conflict detector only, never a write guarantee.
 *
 * @param mixed $form
 */
function wpagent_fluent_forms_full_state_hash( $form ): string {
	$state   = wpagent_fluent_forms_canonicalize_state( wpagent_fluent_forms_model_array( $form ) );
	$encoded = wp_json_encode( $state, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE );
	return hash( 'sha256', false === $encoded ? serialize( $state ) : $encoded );
}

/** @param mixed $value @return array<string,mixed> */
function wpagent_fluent_forms_json_object( $value ): array {
	if ( is_array( $value ) ) {
		return $value;
	}
	if ( ! is_string( $value ) || '' === trim( $value ) ) {
		return array();
	}
	$decoded = json_decode( $value, true );
	return is_array( $decoded ) ? $decoded : array();
}

/** @param mixed $form @return array<string,mixed> */
function wpagent_fluent_forms_form_payload( $form ): array {
	$data = wpagent_fluent_forms_model_array( $form );
	$id   = absint( $data['id'] ?? 0 );
	return array(
		'provider'    => 'fluent-forms',
		'id'          => $id,
		'title'       => sanitize_text_field( (string) ( $data['title'] ?? '' ) ),
		'status'      => sanitize_key( (string) ( $data['status'] ?? '' ) ),
		'type'        => sanitize_key( (string) ( $data['type'] ?? 'form' ) ),
		'has_payment' => ! empty( $data['has_payment'] ),
		'created_at'  => sanitize_text_field( (string) ( $data['created_at'] ?? '' ) ),
		'updated_at'  => sanitize_text_field( (string) ( $data['updated_at'] ?? '' ) ),
		'shortcode'   => sprintf( '[fluentform id="%d"]', $id ),
		'form_fields' => wpagent_fluent_forms_json_object( $data['form_fields'] ?? array() ),
	);
}

/** @return string|WP_Error */
function wpagent_fluent_forms_public_string( $value, int $maximum, string $field, bool $allow_empty = true ) {
	return wpagent_forms_public_string( $value, $maximum, 'fluent_forms', $field, $allow_empty );
}

/**
 * Form fields remain provider-private. We bind their complete serialized
 * representation into fields_hash but expose only the bounded count and
 * submit label. This prevents add-on HTML, actions and credentials from
 * escaping through a generic object schema.
 *
 * @param mixed $form_fields
 * @return array{field_count:int,submit_label:string,fields_hash:string}|WP_Error
 */
function wpagent_fluent_forms_public_fields( $form_fields ) {
	$form_fields = wpagent_forms_public_object( $form_fields, 2, 'fluent_forms', 'form_fields' );
	if ( is_wp_error( $form_fields ) || ! array_key_exists( 'fields', $form_fields ) || ! array_key_exists( 'submitButton', $form_fields ) ) {
		return is_wp_error( $form_fields ) ? $form_fields : wpagent_forms_public_output_error( 'fluent_forms', 'form_fields' );
	}
	$fields = wpagent_forms_public_list( $form_fields['fields'], WPAGENT_FORMS_PUBLIC_MAX_FIELDS, 'fluent_forms', 'form_fields.fields' );
	$button = wpagent_forms_public_object( $form_fields['submitButton'], 100, 'fluent_forms', 'form_fields.submitButton' );
	if ( is_wp_error( $fields ) || is_wp_error( $button ) ) {
		return is_wp_error( $fields ) ? $fields : $button;
	}
	foreach ( $fields as $index => $field ) {
		$field = wpagent_forms_public_object( $field, 100, 'fluent_forms', 'form_fields.fields.' . $index );
		if ( is_wp_error( $field ) ) {
			return $field;
		}
		$encoded_field = wp_json_encode( $field );
		if ( false === $encoded_field || strlen( $encoded_field ) > WPAGENT_FORMS_PUBLIC_MAX_TEXT ) {
			return wpagent_forms_public_output_error( 'fluent_forms', 'form_fields.fields.' . $index );
		}
	}
	$settings = wpagent_forms_public_object( $button['settings'] ?? array(), 100, 'fluent_forms', 'form_fields.submitButton.settings' );
	$button_ui = ! is_wp_error( $settings ) ? wpagent_forms_public_object( $settings['button_ui'] ?? array(), 100, 'fluent_forms', 'form_fields.submitButton.settings.button_ui' ) : $settings;
	$text = ! is_wp_error( $button_ui ) ? wpagent_fluent_forms_public_string( $button_ui['text'] ?? '', 255, 'form_fields.submitButton.settings.button_ui.text' ) : $button_ui;
	if ( is_wp_error( $text ) ) {
		return $text;
	}
	$encoded = wp_json_encode( $form_fields );
	if ( false === $encoded || strlen( $encoded ) > 1048576 ) {
		return wpagent_forms_public_output_error( 'fluent_forms', 'form_fields' );
	}
	return array( 'field_count' => count( $fields ), 'submit_label' => $text, 'fields_hash' => hash( 'sha256', $encoded ) );
}

/** @param mixed $form @return array<string,mixed>|WP_Error */
function wpagent_fluent_forms_public_form_payload( $form ) {
	$data = wpagent_fluent_forms_model_array( $form );
	if ( ! $data || ! ( is_int( $data['id'] ?? null ) || ( is_string( $data['id'] ?? null ) && ctype_digit( $data['id'] ) ) ) ) {
		return wpagent_forms_public_output_error( 'fluent_forms', 'id' );
	}
	foreach ( array( 'title', 'status', 'type', 'created_at', 'updated_at' ) as $key ) {
		if ( isset( $data[ $key ] ) && ! is_string( $data[ $key ] ) ) {
			return wpagent_forms_public_output_error( 'fluent_forms', $key );
		}
	}
	$raw_form_fields = $data['form_fields'] ?? array();
	if ( ! is_array( $raw_form_fields ) && ( ! is_string( $raw_form_fields ) || strlen( $raw_form_fields ) > 1048576 ) ) {
		return wpagent_forms_public_output_error( 'fluent_forms', 'form_fields' );
	}
	$payload = wpagent_fluent_forms_form_payload( $form );
	$public_fields = wpagent_fluent_forms_public_fields( $payload['form_fields'] );
	if ( is_wp_error( $public_fields ) || $payload['id'] <= 0 ) {
		return is_wp_error( $public_fields ) ? $public_fields : wpagent_forms_public_output_error( 'fluent_forms', 'id' );
	}
	$summary = array(
		'provider' => wpagent_fluent_forms_public_string( 'fluent-forms', 32, 'provider', false ),
		'id' => $payload['id'],
		'title' => wpagent_fluent_forms_public_string( $payload['title'], 255, 'title' ),
		'status' => wpagent_fluent_forms_public_string( $payload['status'], 32, 'status' ),
		'type' => wpagent_fluent_forms_public_string( $payload['type'], 32, 'type' ),
		'has_payment' => (bool) $payload['has_payment'],
		'created_at' => wpagent_fluent_forms_public_string( $payload['created_at'], 64, 'created_at' ),
		'updated_at' => wpagent_fluent_forms_public_string( $payload['updated_at'], 64, 'updated_at' ),
		'shortcode' => wpagent_fluent_forms_public_string( $payload['shortcode'], 64, 'shortcode' ),
		'field_count' => $public_fields['field_count'],
		'submit_label' => $public_fields['submit_label'],
		'fields_hash' => $public_fields['fields_hash'],
		'state_hash' => wpagent_fluent_forms_full_state_hash( $form ),
	);
	foreach ( $summary as $value ) {
		if ( is_wp_error( $value ) ) {
			return $value;
		}
	}
	return $summary;
}

/** @param mixed $input @return array{service:object,form:mixed}|WP_Error */
function wpagent_fluent_forms_get_authorized_form( $input ) {
	$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 ( ! wpagent_fluent_forms_has_permission( 'fluentform_forms_manager', $form_id ) ) {
		return new WP_Error( 'wpagent_forbidden', __( 'Keine Berechtigung für dieses Fluent-Forms-Formular.', 'wp-agent-companion' ), array( 'status' => 403 ) );
	}
	$service = wpagent_fluent_forms_service();
	if ( is_wp_error( $service ) ) {
		return $service;
	}
	try {
		$form = $service->find( $form_id );
	} catch ( Throwable $error ) {
		return new WP_Error( 'wpagent_fluent_form_not_found', __( 'Fluent-Forms-Formular nicht gefunden.', 'wp-agent-companion' ), array( 'status' => 404 ) );
	}
	if ( ! wpagent_fluent_forms_model_array( $form ) ) {
		return new WP_Error( 'wpagent_fluent_form_not_found', __( 'Fluent-Forms-Formular nicht gefunden.', 'wp-agent-companion' ), array( 'status' => 404 ) );
	}
	return array( 'service' => $service, 'form' => $form );
}

/** @return array<string,mixed> */
function wpagent_fluent_forms_form_output_schema(): array {
	return wpagent_forms_public_object_schema(
		array(
			'provider' => wpagent_forms_public_string_schema( 32, true ), 'id' => array( 'type' => 'integer', 'minimum' => 1 ),
			'title' => wpagent_forms_public_string_schema( 255 ), 'status' => wpagent_forms_public_string_schema( 32 ),
			'type' => wpagent_forms_public_string_schema( 32 ), 'has_payment' => array( 'type' => 'boolean' ),
			'created_at' => wpagent_forms_public_string_schema( 64 ), 'updated_at' => wpagent_forms_public_string_schema( 64 ),
			'shortcode' => wpagent_forms_public_string_schema( 64 ), 'field_count' => array( 'type' => 'integer', 'minimum' => 0, 'maximum' => WPAGENT_FORMS_PUBLIC_MAX_FIELDS ),
			'submit_label' => wpagent_forms_public_string_schema( 255 ),
			'fields_hash' => wpagent_forms_public_hash_schema(), 'state_hash' => wpagent_forms_public_hash_schema(),
		),
		array( 'provider', 'id', 'title', 'status', 'type', 'has_payment', 'created_at', 'updated_at', 'shortcode', 'field_count', 'submit_label', 'fields_hash', 'state_hash' )
	);
}

/** @param mixed $fields @return string|WP_Error */
function wpagent_fluent_forms_encode_fields( $fields ) {
	if ( ! is_array( $fields ) || ! isset( $fields['fields'], $fields['submitButton'] ) || ! is_array( $fields['fields'] ) || ! is_array( $fields['submitButton'] ) ) {
		return new WP_Error( 'wpagent_fluent_forms_bad_fields', __( 'form_fields benötigt die Objekte fields und submitButton.', 'wp-agent-companion' ), array( 'status' => 400 ) );
	}
	$encoded = wp_json_encode( $fields );
	if ( false === $encoded || strlen( $encoded ) > 1048576 ) {
		return new WP_Error( 'wpagent_fluent_forms_bad_fields', __( 'form_fields ist ungültig oder größer als 1 MiB.', 'wp-agent-companion' ), array( 'status' => 400 ) );
	}
	return $encoded;
}
