<?php
/** Strict recursive validation for writable ACF field values. */

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

/** @return mixed|WP_Error */
function wpagent_acf_value_validate( array $field, $value, int $depth = 0, int &$nodes = 0, array $context = array() ) {
	if ( $depth > 6 || ++$nodes > WPAGENT_ACF_VALUE_MAX_NODES ) {
		return new WP_Error( 'wpagent_acf_value_too_complex', __( 'Der ACF-Wert ist zu tief verschachtelt oder enthält zu viele Elemente.', 'wp-agent-companion' ), array( 'status' => 400 ) );
	}
	if ( 0 === $depth ) {
		$json = wp_json_encode( $value );
		if ( false === $json || strlen( $json ) > WPAGENT_ACF_VALUE_MAX_BYTES ) {
			return new WP_Error( 'wpagent_acf_value_too_large', __( 'Der ACF-Wert ist ungültig oder größer als 100 KB.', 'wp-agent-companion' ), array( 'status' => 400 ) );
		}
	}
	$type = (string) ( $field['type'] ?? '' );
	if ( ! wpagent_acf_value_is_supported_for_context( $field, $context, $depth ) ) {
		return new WP_Error( 'wpagent_acf_value_unsupported', __( 'Dieser ACF-Feldtyp besitzt keinen verlustfreien sicheren Schreibpfad.', 'wp-agent-companion' ), array( 'status' => 409, 'field_type' => $type ) );
	}
	if ( wpagent_acf_value_is_empty( $value ) ) {
		if ( ! empty( $field['required'] ) || ( wpagent_acf_value_has_rows( $field ) && absint( $field['min'] ?? 0 ) > 0 ) ) {
			return wpagent_acf_value_invalid();
		}
		if ( 'group' !== $type ) {
			return wpagent_acf_value_empty_result( $field, $value );
		}
	}

	if ( in_array( $type, array( 'text', 'textarea' ), true ) ) {
		$configured_max = absint( $field['maxlength'] ?? 0 );
		$max_length     = $configured_max > 0 ? min( 10000, $configured_max ) : 10000;
		$length         = is_string( $value ) && function_exists( 'mb_strlen' ) ? mb_strlen( $value, 'UTF-8' ) : ( is_string( $value ) ? strlen( $value ) : $max_length + 1 );
		if ( ! is_string( $value ) || $length > $max_length || wp_check_invalid_utf8( $value ) !== $value ) {
			return wpagent_acf_value_invalid();
		}
		$sanitized = 'text' === $type ? sanitize_text_field( $value ) : sanitize_textarea_field( $value );
		return $sanitized === $value ? $value : wpagent_acf_value_invalid();
	}
	if ( 'email' === $type ) {
		$email = is_string( $value ) ? sanitize_email( $value ) : '';
		return '' !== $email && $email === $value ? $email : wpagent_acf_value_invalid();
	}
	if ( 'url' === $type ) {
		$url = is_string( $value ) ? esc_url_raw( $value, array( 'http', 'https' ) ) : '';
		return '' !== $url && $url === $value ? $url : wpagent_acf_value_invalid();
	}
	if ( in_array( $type, array( 'number', 'range' ), true ) ) {
		if ( ! is_int( $value ) && ! is_float( $value ) && ! ( is_string( $value ) && preg_match( '/^-?(?:0|[1-9][0-9]*)(?:\.[0-9]+)?$/', $value ) ) ) return wpagent_acf_value_invalid();
		$number = (float) $value;
		if ( ! is_finite( $number ) ) return wpagent_acf_value_invalid();
		if ( '' !== (string) ( $field['min'] ?? '' ) && $number < (float) $field['min'] ) return wpagent_acf_value_invalid();
		if ( '' !== (string) ( $field['max'] ?? '' ) && $number > (float) $field['max'] ) return wpagent_acf_value_invalid();
		$step = (string) ( $field['step'] ?? '' );
		if ( '' !== $step && (float) $step > 0 ) {
			$origin    = '' !== (string) ( $field['min'] ?? '' ) ? (float) $field['min'] : 0.0;
			$remainder = fmod( abs( $number - $origin ), (float) $step );
			$tolerance = max( 1.0, abs( $number ), abs( $origin ) ) * PHP_FLOAT_EPSILON * 16;
			if ( $remainder > $tolerance && abs( $remainder - (float) $step ) > $tolerance ) return wpagent_acf_value_invalid();
		}
		return $value;
	}
	if ( 'true_false' === $type ) {
		return is_bool( $value ) ? $value : wpagent_acf_value_invalid();
	}
	if ( 'color_picker' === $type ) {
		$color = is_string( $value ) ? sanitize_hex_color( $value ) : null;
		return $color && strtolower( $color ) === strtolower( $value ) ? strtolower( $color ) : wpagent_acf_value_invalid();
	}
	if ( in_array( $type, array( 'date_picker', 'date_time_picker', 'time_picker' ), true ) ) {
		$formats = array( 'date_picker' => 'Ymd', 'date_time_picker' => 'Y-m-d H:i:s', 'time_picker' => 'H:i:s' );
		return is_string( $value ) && wpagent_acf_value_valid_date( $formats[ $type ], $value ) ? $value : wpagent_acf_value_invalid();
	}
	if ( in_array( $type, array( 'select', 'radio', 'button_group', 'checkbox' ), true ) ) {
		return wpagent_acf_value_validate_choice( $field, $value, 'checkbox' === $type || ! empty( $field['multiple'] ) );
	}
	if ( in_array( $type, array( 'relationship', 'post_object', 'page_link' ), true ) ) {
		return wpagent_acf_value_validate_posts( $field, $value, 'relationship' === $type || ! empty( $field['multiple'] ) );
	}
	if ( in_array( $type, array( 'image', 'file', 'gallery' ), true ) ) {
		return wpagent_acf_value_validate_attachments( $field, $value, 'gallery' === $type, 'post' === ( $context['object_type'] ?? '' ) ? absint( $context['post_id'] ?? 0 ) : 0 );
	}
	if ( 'taxonomy' === $type ) {
		return wpagent_acf_value_validate_terms( $field, $value, ! empty( $field['multiple'] ) || in_array( (string) ( $field['field_type'] ?? '' ), array( 'checkbox', 'multi_select' ), true ) );
	}
	if ( 'user' === $type ) {
		return wpagent_acf_value_validate_users( $field, $value, ! empty( $field['multiple'] ) );
	}
	if ( 'group' === $type ) {
		return wpagent_acf_value_validate_row( $field['sub_fields'] ?? array(), $value, $depth + 1, $nodes, array(), $context );
	}
	if ( 'repeater' === $type ) {
		return wpagent_acf_value_validate_rows( $field['sub_fields'] ?? array(), $value, $field, $depth + 1, $nodes, $context );
	}
	if ( 'flexible_content' === $type ) {
		if ( ! is_array( $value ) || ! array_is_list( $value ) || count( $value ) > 100 ) return wpagent_acf_value_invalid();
		$rows = array();
		foreach ( $value as $row ) {
			$name = is_array( $row ) ? (string) ( $row['acf_fc_layout'] ?? '' ) : '';
			$layout = wpagent_acf_value_layout( $field, $name );
			if ( ! $layout ) return wpagent_acf_value_invalid();
			$clean = wpagent_acf_value_validate_row( $layout['sub_fields'] ?? array(), $row, $depth + 1, $nodes, array( 'acf_fc_layout' ), $context );
			if ( is_wp_error( $clean ) ) return $clean;
			$clean['acf_fc_layout'] = (string) ( $layout['name'] ?? $name );
			$rows[] = $clean;
		}
		return $rows;
	}
	return wpagent_acf_value_invalid();
}

function wpagent_acf_value_has_rows( array $field ): bool {
	$type = (string) ( $field['type'] ?? '' );
	return in_array( $type, array( 'relationship', 'gallery', 'repeater', 'flexible_content' ), true ) || ! empty( $field['multiple'] );
}

/** @return WP_Error */
function wpagent_acf_value_invalid() {
	return new WP_Error( 'wpagent_acf_value_invalid', __( 'Der Wert entspricht nicht der registrierten ACF-Felddefinition.', 'wp-agent-companion' ), array( 'status' => 400 ) );
}

function wpagent_acf_value_is_empty( $value ): bool {
	return function_exists( 'acf_is_empty' ) ? acf_is_empty( $value ) : ( ! $value && ! is_numeric( $value ) );
}

/** Return the canonical storage representation for an optional empty value. */
function wpagent_acf_value_empty_result( array $field, $value ) {
	$type = (string) ( $field['type'] ?? '' );
	if ( in_array( $type, array( 'repeater', 'flexible_content', 'checkbox', 'relationship', 'gallery' ), true ) || ! empty( $field['multiple'] ) ) return array();
	if ( in_array( $type, array( 'post_object', 'page_link', 'image', 'file', 'taxonomy', 'user' ), true ) ) return null;
	if ( 'true_false' === $type ) return false;
	return is_string( $value ) ? '' : null;
}

function wpagent_acf_value_valid_date( string $format, string $value ): bool {
	$date   = DateTimeImmutable::createFromFormat( '!' . $format, $value );
	$errors = DateTimeImmutable::getLastErrors();
	return false !== $date
		&& ( false === $errors || ( 0 === $errors['warning_count'] && 0 === $errors['error_count'] ) )
		&& $date->format( $format ) === $value;
}

/** @return mixed|WP_Error */
function wpagent_acf_value_validate_choice( array $field, $value, bool $multiple ) {
	$choices = is_array( $field['choices'] ?? null ) ? array_map( 'strval', array_keys( $field['choices'] ) ) : array();
	$values  = $multiple ? $value : array( $value );
	if ( ! is_array( $values ) || ( $multiple && ! array_is_list( $values ) ) || count( $values ) > 100 ) return wpagent_acf_value_invalid();
	$result = array();
	foreach ( $values as $item ) {
		$item = is_scalar( $item ) ? (string) $item : '';
		if ( '' === $item || ! in_array( $item, $choices, true ) ) return wpagent_acf_value_invalid();
		$result[] = $item;
	}
	return $multiple ? array_values( array_unique( $result ) ) : $result[0];
}

/** @return mixed|WP_Error */
function wpagent_acf_value_validate_posts( array $field, $value, bool $multiple ) {
	$values = $multiple ? $value : array( $value );
	$limit  = min( 100, max( 1, absint( $field['max'] ?? 100 ) ?: 100 ) );
	if ( ! is_array( $values ) || ( $multiple && ! array_is_list( $values ) ) || count( $values ) > $limit ) return wpagent_acf_value_invalid();
	if ( count( $values ) < absint( $field['min'] ?? 0 ) ) return wpagent_acf_value_invalid();
	$allowed_types = is_array( $field['post_type'] ?? null ) ? $field['post_type'] : ( empty( $field['post_type'] ) ? array() : array( $field['post_type'] ) );
	$result = array();
	foreach ( $values as $item ) {
		$id = filter_var( $item, FILTER_VALIDATE_INT, array( 'options' => array( 'min_range' => 1 ) ) );
		$post = $id ? get_post( (int) $id ) : null;
		if ( ! $post || ( $allowed_types && ! in_array( $post->post_type, $allowed_types, true ) ) ) return wpagent_acf_value_invalid();
		if ( ! current_user_can( 'read_post', $post->ID ) ) return new WP_Error( 'wpagent_acf_reference_forbidden', __( 'Mindestens ein referenzierter Beitrag darf mit diesem Konto nicht gelesen werden.', 'wp-agent-companion' ), array( 'status' => 403 ) );
		$result[] = $post->ID;
	}
	return $multiple ? array_values( array_unique( $result ) ) : ( $result[0] ?? null );
}

/** @return mixed|WP_Error */
function wpagent_acf_value_validate_attachments( array $field, $value, bool $multiple, int $post_id ) {
	$reference_field              = $field;
	$reference_field['post_type'] = array( 'attachment' );
	$reference_field['max']       = $multiple ? ( $field['max'] ?? 100 ) : 1;
	$result = wpagent_acf_value_validate_posts( $reference_field, $value, $multiple );
	if ( is_wp_error( $result ) ) return $result;
	$ids = $multiple ? $result : array( $result );
	if ( 'uploadedTo' === (string) ( $field['library'] ?? '' ) ) {
		foreach ( $ids as $id ) {
			if ( $id && (int) get_post_field( 'post_parent', $id ) !== $post_id ) return wpagent_acf_value_invalid();
		}
	}
	if ( 'image' === (string) ( $field['type'] ?? '' ) || 'gallery' === (string) ( $field['type'] ?? '' ) ) {
		foreach ( $ids as $id ) if ( $id && ! wp_attachment_is_image( $id ) ) return wpagent_acf_value_invalid();
	}
	if ( function_exists( 'acf_validate_attachment' ) ) {
		foreach ( $ids as $id ) {
			if ( ! $id ) continue;
			$attachment = wp_prepare_attachment_for_js( $id );
			$errors     = $attachment ? acf_validate_attachment( $attachment, $field, 'prepare' ) : array( 'invalid' );
			if ( ! empty( $errors ) ) return wpagent_acf_value_invalid();
		}
	}
	return $result;
}

/** @return mixed|WP_Error */
function wpagent_acf_value_validate_terms( array $field, $value, bool $multiple ) {
	$values = $multiple ? $value : array( $value );
	if ( ! is_array( $values ) || ( $multiple && ! array_is_list( $values ) ) || count( $values ) > 100 ) return wpagent_acf_value_invalid();
	$allowed = is_array( $field['taxonomy'] ?? null ) ? $field['taxonomy'] : ( empty( $field['taxonomy'] ) ? array() : array( $field['taxonomy'] ) );
	if ( ! $allowed ) return wpagent_acf_value_invalid();
	foreach ( $allowed as $taxonomy_name ) {
		$taxonomy = get_taxonomy( (string) $taxonomy_name );
		if ( ! $taxonomy || ! current_user_can( $taxonomy->cap->assign_terms ) ) {
			return new WP_Error( 'wpagent_acf_taxonomy_forbidden', __( 'Für mindestens eine konfigurierte Taxonomie fehlt die Berechtigung zum Zuweisen von Begriffen.', 'wp-agent-companion' ), array( 'status' => 403 ) );
		}
	}
	$result = array();
	foreach ( $values as $item ) {
		$id = filter_var( $item, FILTER_VALIDATE_INT, array( 'options' => array( 'min_range' => 1 ) ) );
		$term = $id ? get_term( (int) $id ) : null;
		if ( ! $term instanceof WP_Term || ( $allowed && ! in_array( $term->taxonomy, $allowed, true ) ) ) return wpagent_acf_value_invalid();
		$result[] = $term->term_id;
	}
	return $multiple ? array_values( array_unique( $result ) ) : ( $result[0] ?? null );
}

/** @return mixed|WP_Error */
function wpagent_acf_value_validate_users( array $field, $value, bool $multiple ) {
	if ( ! current_user_can( 'list_users' ) ) return new WP_Error( 'wpagent_acf_user_forbidden', __( 'Benutzerfelder benötigen die Berechtigung zum Auflisten von Benutzern.', 'wp-agent-companion' ), array( 'status' => 403 ) );
	$values = $multiple ? $value : array( $value );
	if ( ! is_array( $values ) || ( $multiple && ! array_is_list( $values ) ) || count( $values ) > 100 ) return wpagent_acf_value_invalid();
	$result = array();
	$roles  = is_array( $field['role'] ?? null ) ? array_map( 'strval', $field['role'] ) : ( empty( $field['role'] ) ? array() : array( (string) $field['role'] ) );
	foreach ( $values as $item ) {
		$id = filter_var( $item, FILTER_VALIDATE_INT, array( 'options' => array( 'min_range' => 1 ) ) );
		$user = $id ? get_user_by( 'id', (int) $id ) : false;
		if ( ! $user || ( $roles && ! array_intersect( $roles, $user->roles ) ) ) return wpagent_acf_value_invalid();
		$result[] = (int) $id;
	}
	return $multiple ? array_values( array_unique( $result ) ) : ( $result[0] ?? null );
}

/** @return array<string,mixed>|WP_Error */
function wpagent_acf_value_validate_row( $sub_fields, $value, int $depth, int &$nodes, array $allowed_extra = array(), array $context = array() ) {
	if ( ! is_array( $value ) || array_is_list( $value ) ) return wpagent_acf_value_invalid();
	$children = array();
	foreach ( is_array( $sub_fields ) ? $sub_fields : array() as $child ) {
		if ( ! is_array( $child ) || '' === (string) ( $child['name'] ?? '' ) ) continue;
		$children[ (string) $child['name'] ] = $child;
		$children[ (string) $child['key'] ] = $child;
	}
	$result = array();
	$seen   = array();
	foreach ( $value as $key => $item ) {
		if ( in_array( (string) $key, $allowed_extra, true ) ) continue;
		if ( ! isset( $children[ $key ] ) ) return wpagent_acf_value_invalid();
		$child = $children[ $key ];
		$child_key = (string) ( $child['key'] ?? '' );
		if ( isset( $seen[ $child_key ] ) ) return wpagent_acf_value_invalid();
		$seen[ $child_key ] = true;
		$clean = wpagent_acf_value_validate( $child, $item, $depth, $nodes, $context );
		if ( is_wp_error( $clean ) ) return $clean;
		$result[ (string) $child['name'] ] = $clean;
	}
	foreach ( is_array( $sub_fields ) ? $sub_fields : array() as $child ) {
		if ( is_array( $child ) && ! isset( $seen[ (string) ( $child['key'] ?? '' ) ] ) ) return wpagent_acf_value_invalid();
	}
	return $result;
}

/** @return array<int,array<string,mixed>>|WP_Error */
function wpagent_acf_value_validate_rows( $sub_fields, $value, array $field, int $depth, int &$nodes, array $context = array() ) {
	$limit = min( 100, max( 1, absint( $field['max'] ?? 100 ) ?: 100 ) );
	if ( ! is_array( $value ) || ! array_is_list( $value ) || count( $value ) > $limit ) return wpagent_acf_value_invalid();
	if ( count( $value ) < absint( $field['min'] ?? 0 ) ) return wpagent_acf_value_invalid();
	$rows = array();
	foreach ( $value as $row ) {
		$clean = wpagent_acf_value_validate_row( $sub_fields, $row, $depth, $nodes, array(), $context );
		if ( is_wp_error( $clean ) ) return $clean;
		$rows[] = $clean;
	}
	return $rows;
}
