<?php
/** Bounded, provider-bound content-model snapshot primitives. */

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

define( 'WPAGENT_CONTENT_MODEL_SNAPSHOT_VERSION', 1 );
define( 'WPAGENT_CONTENT_MODEL_SNAPSHOT_MAX_FIELDS', 20 );
define( 'WPAGENT_CONTENT_MODEL_SNAPSHOT_MAX_BYTES', 65536 );
define( 'WPAGENT_CONTENT_MODEL_SNAPSHOT_MAX_NODES', 500 );

/** @return string[] */
function wpagent_content_model_providers(): array {
	return array( 'acf', 'pods', 'acpt-lite', 'meta-box' );
}

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

/** @param mixed $value */
function wpagent_content_model_hash( $value ): string {
	$encoded = wp_json_encode( wpagent_content_model_canonicalize( $value ), JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE );
	return hash( 'sha256', false === $encoded ? serialize( $value ) : $encoded );
}

function wpagent_content_model_site_binding(): string {
	return hash( 'sha256', get_current_blog_id() . "\0" . home_url( '/' ) . "\0" . site_url( '/' ) );
}

/** @return array<string,bool> */
function wpagent_content_model_capabilities( string $provider ): array {
	$available = false;
	if ( 'acf' === $provider ) {
		$available = ! is_wp_error( wpagent_acf_group_require_api() );
	} elseif ( 'pods' === $provider ) {
		$available = ! is_wp_error( wpagent_pods_require_api() );
	} elseif ( 'acpt-lite' === $provider ) {
		$available = ! is_wp_error( wpagent_acpt_meta_provider() );
	} elseif ( 'meta-box' === $provider ) {
		$group = wpagent_metabox_native_ability( 'meta-box/get-field-group' );
		$fields = wpagent_metabox_native_ability( 'meta-box/list-fields' );
		$available = ! is_wp_error( $group ) && ! is_wp_error( $fields );
	}
	return array(
		'snapshot' => $available,
		'compare' => true,
		'migration_source' => $available && in_array( $provider, array( 'acf', 'acpt-lite' ), true ),
		'migration_target' => $available && in_array( $provider, array( 'acf', 'acpt-lite' ), true ),
	);
}

function wpagent_content_model_provider_version( string $provider ): string {
	if ( 'acf' === $provider ) {
		return defined( 'ACF_VERSION' ) ? (string) ACF_VERSION : ( function_exists( 'acf_get_setting' ) ? (string) acf_get_setting( 'version' ) : '' );
	}
	if ( 'pods' === $provider ) {
		return defined( 'PODS_VERSION' ) ? (string) PODS_VERSION : '';
	}
	if ( 'acpt-lite' === $provider ) {
		$provider_data = wpagent_acpt_meta_provider();
		return is_wp_error( $provider_data ) ? '' : (string) $provider_data['version'];
	}
	if ( 'meta-box' === $provider ) {
		return defined( 'RWMB_VER' ) ? (string) RWMB_VER : '';
	}
	return '';
}

/** @return string|WP_Error */
function wpagent_content_model_identifier( string $provider, $identifier ) {
	$identifier = is_string( $identifier ) ? $identifier : '';
	if ( 'acf' === $provider && preg_match( '/^group_[A-Za-z0-9_-]{1,120}$/D', $identifier ) ) {
		return $identifier;
	}
	if ( 'pods' === $provider && preg_match( '/^[a-z][a-z0-9_]{0,63}$/D', $identifier ) ) {
		return $identifier;
	}
	if ( 'acpt-lite' === $provider ) {
		return wpagent_acpt_meta_identifier( $identifier, 'identifier' );
	}
	if ( 'meta-box' === $provider && preg_match( '/^[1-9][0-9]{0,9}$/D', $identifier ) ) {
		return $identifier;
	}
	return new WP_Error( 'wpagent_content_model_identifier', __( 'Der Datenmodellbezeichner ist für diesen Anbieter ungültig.', 'wp-agent-companion' ), array( 'status' => 400 ) );
}

/** @param mixed $value */
function wpagent_content_model_within_limits( $value, int $depth = 0, int &$nodes = 0 ): bool {
	if ( ++$nodes > WPAGENT_CONTENT_MODEL_SNAPSHOT_MAX_NODES || $depth > 10 ) {
		return false;
	}
	if ( is_string( $value ) ) {
		return strlen( $value ) <= 2000;
	}
	if ( is_null( $value ) || is_bool( $value ) || is_int( $value ) || is_float( $value ) ) {
		return is_float( $value ) ? is_finite( $value ) : true;
	}
	if ( ! is_array( $value ) ) {
		return false;
	}
	foreach ( $value as $key => $item ) {
		if ( ! is_int( $key ) && ( ! is_string( $key ) || strlen( $key ) > 100 ) ) {
			return false;
		}
		if ( ! wpagent_content_model_within_limits( $item, $depth + 1, $nodes ) ) {
			return false;
		}
	}
	return true;
}

/** @return array<string,mixed>|WP_Error */
function wpagent_content_model_finish_snapshot( array $snapshot ): array {
	$nodes = 0;
	if ( ! wpagent_content_model_within_limits( $snapshot, 0, $nodes ) ) {
		return new WP_Error( 'wpagent_content_model_too_large', __( 'Der Datenmodell-Snapshot überschreitet die sichere Strukturgrenze.', 'wp-agent-companion' ), array( 'status' => 409 ) );
	}
	$encoded = wp_json_encode( $snapshot, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE );
	if ( false === $encoded || strlen( $encoded ) > WPAGENT_CONTENT_MODEL_SNAPSHOT_MAX_BYTES ) {
		return new WP_Error( 'wpagent_content_model_too_large', __( 'Der Datenmodell-Snapshot überschreitet die sichere Größengrenze.', 'wp-agent-companion' ), array( 'status' => 409 ) );
	}
	$snapshot['snapshot_hash'] = wpagent_content_model_hash( $snapshot );
	return $snapshot;
}

/** @return array<string,mixed> */
function wpagent_content_model_absent_snapshot( string $provider, string $identifier, array $capabilities ): array {
	return array(
		'schema_version' => WPAGENT_CONTENT_MODEL_SNAPSHOT_VERSION,
		'site_binding' => wpagent_content_model_site_binding(),
		'provider' => $provider,
		'provider_version' => wpagent_content_model_provider_version( $provider ),
		'capabilities' => $capabilities,
		'identifier' => $identifier,
		'exists' => false,
		'kind' => 'absent',
		'label' => '',
		'target' => array( 'type' => '', 'names' => array() ),
		'settings' => array(),
		'fields' => array(),
		'provider_hash' => wpagent_content_model_hash( array( 'provider' => $provider, 'identifier' => $identifier, 'exists' => false, 'site_binding' => wpagent_content_model_site_binding() ) ),
		'warnings' => $capabilities['snapshot'] ? array() : array( 'provider_unavailable' ),
	);
}

/** @return array<int,array<string,mixed>>|WP_Error */
function wpagent_content_model_normalize_acf_fields( array $fields ): array {
	if ( count( $fields ) > WPAGENT_CONTENT_MODEL_SNAPSHOT_MAX_FIELDS ) {
		return new WP_Error( 'wpagent_content_model_field_limit', __( 'Die ACF-Feldgruppe enthält mehr Felder als der sichere Datenmodell-Snapshot erlaubt.', 'wp-agent-companion' ), array( 'status' => 409 ) );
	}
	$result = array();
	foreach ( $fields as $field ) {
		if ( ! is_array( $field ) ) {
			return new WP_Error( 'wpagent_content_model_shape', __( 'ACF lieferte eine beschädigte Felddefinition.', 'wp-agent-companion' ), array( 'status' => 500 ) );
		}
		$state = wpagent_acf_group_field_state( $field );
		$choices = array();
		foreach ( is_array( $state['choices'] ?? null ) ? $state['choices'] : array() as $value => $label ) {
			$choices[] = array( 'label' => (string) $label, 'value' => (string) $value );
		}
		$result[] = array(
			'name' => (string) $state['name'], 'label' => (string) $state['label'], 'type' => (string) $state['type'],
			'required' => (bool) $state['required'], 'options' => $choices,
			'settings' => 'select' === $state['type'] ? array( 'multiple' => (bool) ( $state['multiple'] ?? false ) ) : array(),
		);
	}
	return $result;
}

/** @return array<string,mixed>|WP_Error */
function wpagent_content_model_snapshot_acf( string $identifier, array $capabilities ) {
	$group = wpagent_acf_group_get_persistent( $identifier );
	if ( is_wp_error( $group ) ) {
		return in_array( 'wpagent_acf_group_not_found', $group->get_error_codes(), true ) ? wpagent_content_model_finish_snapshot( wpagent_content_model_absent_snapshot( 'acf', $identifier, $capabilities ) ) : $group;
	}
	$state = wpagent_acf_group_state( $group );
	$fields = acf_get_fields( $group );
	$normalized = wpagent_content_model_normalize_acf_fields( is_array( $fields ) ? $fields : array() );
	if ( is_wp_error( $normalized ) ) {
		return $normalized;
	}
	return wpagent_content_model_finish_snapshot(
		array(
			'schema_version' => WPAGENT_CONTENT_MODEL_SNAPSHOT_VERSION, 'site_binding' => wpagent_content_model_site_binding(),
			'provider' => 'acf', 'provider_version' => wpagent_content_model_provider_version( 'acf' ), 'capabilities' => $capabilities,
			'identifier' => $identifier, 'exists' => true, 'kind' => 'field_group', 'label' => (string) $state['title'],
			'target' => array( 'type' => 'post_type', 'names' => $state['post_types'] ), 'settings' => array( 'enabled' => (bool) $state['active'] ),
			'fields' => $normalized, 'provider_hash' => (string) $state['hash'], 'warnings' => array(),
		)
	);
}

/** @return array<string,mixed>|WP_Error */
function wpagent_content_model_snapshot_pods( string $identifier, array $capabilities ) {
	$pod = wpagent_pods_get_model( $identifier );
	if ( is_wp_error( $pod ) ) {
		return in_array( 'wpagent_pods_not_found', $pod->get_error_codes(), true ) ? wpagent_content_model_finish_snapshot( wpagent_content_model_absent_snapshot( 'pods', $identifier, $capabilities ) ) : $pod;
	}
	$state = wpagent_pods_model_state( $pod );
	if ( count( $state['fields'] ) > WPAGENT_CONTENT_MODEL_SNAPSHOT_MAX_FIELDS ) {
		return new WP_Error( 'wpagent_content_model_field_limit', __( 'Das Pods-Modell enthält mehr Felder als der sichere Datenmodell-Snapshot erlaubt.', 'wp-agent-companion' ), array( 'status' => 409 ) );
	}
	$fields = array();
	foreach ( $state['fields'] as $field ) {
		$fields[] = array(
			'name' => (string) $field['name'], 'label' => (string) $field['label'], 'type' => (string) $field['type'],
			'required' => (bool) $field['required'], 'options' => array(),
			'settings' => array( 'multiple' => (bool) $field['multiple'], 'repeatable' => (bool) $field['repeatable'], 'relationship_object' => (string) $field['relationship_object'], 'relationship_target' => (string) $field['relationship_target'] ),
		);
	}
	return wpagent_content_model_finish_snapshot(
		array(
			'schema_version' => WPAGENT_CONTENT_MODEL_SNAPSHOT_VERSION, 'site_binding' => wpagent_content_model_site_binding(),
			'provider' => 'pods', 'provider_version' => wpagent_content_model_provider_version( 'pods' ), 'capabilities' => $capabilities,
			'identifier' => $identifier, 'exists' => true, 'kind' => 'model', 'label' => (string) $state['label'],
			'target' => array( 'type' => (string) $state['kind'], 'names' => array( (string) $state['name'] ) ),
			'settings' => array( 'enabled' => true, 'storage' => (string) $state['storage'], 'public' => (bool) $state['public'], 'hierarchical' => (bool) $state['hierarchical'], 'show_in_rest' => (bool) $state['show_in_rest'] ),
			'fields' => $fields, 'provider_hash' => (string) $state['hash'], 'warnings' => array(),
		)
	);
}

/** @return array<string,mixed>|WP_Error */
function wpagent_content_model_snapshot_acpt( string $identifier, array $capabilities ) {
	$state = wpagent_acpt_meta_group_state( $identifier );
	if ( is_wp_error( $state ) ) {
		return in_array( 'wpagent_acpt_meta_not_found', $state->get_error_codes(), true ) ? wpagent_content_model_finish_snapshot( wpagent_content_model_absent_snapshot( 'acpt-lite', $identifier, $capabilities ) ) : $state;
	}
	$fields = array();
	foreach ( $state['boxes'] as $box ) {
		foreach ( $box['fields'] as $field ) {
			$fields[] = array(
				'name' => (string) $field['name'], 'label' => (string) $field['label'], 'type' => (string) $field['type'],
				'required' => (bool) $field['required'], 'options' => array_map( static fn( array $option ): array => array( 'label' => (string) $option['label'], 'value' => (string) $option['value'] ), $field['options'] ),
				'settings' => 'select' === $field['type'] ? array( 'multiple' => false ) : array(),
			);
		}
	}
	if ( count( $fields ) > WPAGENT_CONTENT_MODEL_SNAPSHOT_MAX_FIELDS ) {
		return new WP_Error( 'wpagent_content_model_field_limit', __( 'Die ACPT-Lite-Metagruppe enthält mehr Felder als der sichere Datenmodell-Snapshot erlaubt.', 'wp-agent-companion' ), array( 'status' => 409 ) );
	}
	return wpagent_content_model_finish_snapshot(
		array(
			'schema_version' => WPAGENT_CONTENT_MODEL_SNAPSHOT_VERSION, 'site_binding' => wpagent_content_model_site_binding(),
			'provider' => 'acpt-lite', 'provider_version' => wpagent_content_model_provider_version( 'acpt-lite' ), 'capabilities' => $capabilities,
			'identifier' => $identifier, 'exists' => true, 'kind' => 'field_group', 'label' => (string) $state['label'],
			'target' => array( 'type' => (string) $state['target']['type'], 'names' => array( (string) $state['target']['name'] ) ), 'settings' => array( 'enabled' => true ),
			'fields' => $fields, 'provider_hash' => (string) $state['definition_hash'], 'warnings' => array(),
		)
	);
}

/** @return array<string,mixed>|WP_Error */
function wpagent_content_model_snapshot_meta_box( string $identifier, array $capabilities ) {
	if ( ! $capabilities['snapshot'] ) {
		return wpagent_content_model_finish_snapshot( wpagent_content_model_absent_snapshot( 'meta-box', $identifier, $capabilities ) );
	}
	$post = wpagent_metabox_field_group( (int) $identifier );
	if ( is_wp_error( $post ) ) {
		return wpagent_content_model_finish_snapshot( wpagent_content_model_absent_snapshot( 'meta-box', $identifier, $capabilities ) );
	}
	$native = wpagent_metabox_execute_field_operation_locked( 'list-meta-box-fields', array(), $post );
	if ( is_wp_error( $native ) || ! is_array( $native ) || ! is_array( $native['fields'] ?? null ) || count( $native['fields'] ) > WPAGENT_CONTENT_MODEL_SNAPSHOT_MAX_FIELDS ) {
		return new WP_Error( 'wpagent_content_model_meta_box_shape', __( 'Meta Box lieferte keine begrenzte, vollständige Feldgruppenstruktur.', 'wp-agent-companion' ), array( 'status' => 409 ) );
	}
	$fields = array();
	foreach ( $native['fields'] as $field ) {
		if ( ! is_array( $field ) || ! is_string( $field['id'] ?? null ) || ! is_string( $field['type'] ?? null ) ) {
			return new WP_Error( 'wpagent_content_model_meta_box_shape', __( 'Meta Box lieferte eine beschädigte Felddefinition.', 'wp-agent-companion' ), array( 'status' => 409 ) );
		}
		$options = array();
		foreach ( is_array( $field['options'] ?? null ) ? $field['options'] : array() as $value => $label ) {
			$options[] = array( 'label' => is_scalar( $label ) ? (string) $label : '', 'value' => is_scalar( $value ) ? (string) $value : '' );
		}
		$fields[] = array( 'name' => $field['id'], 'label' => is_string( $field['name'] ?? null ) ? $field['name'] : $field['id'], 'type' => $field['type'], 'required' => ! empty( $field['required'] ), 'options' => $options, 'settings' => array() );
	}
	return wpagent_content_model_finish_snapshot(
		array(
			'schema_version' => WPAGENT_CONTENT_MODEL_SNAPSHOT_VERSION, 'site_binding' => wpagent_content_model_site_binding(),
			'provider' => 'meta-box', 'provider_version' => wpagent_content_model_provider_version( 'meta-box' ), 'capabilities' => $capabilities,
			'identifier' => $identifier, 'exists' => true, 'kind' => 'field_group', 'label' => $post->post_title,
			'target' => array( 'type' => '', 'names' => array() ), 'settings' => array( 'enabled' => 'publish' === $post->post_status ),
			'fields' => $fields, 'provider_hash' => wpagent_content_model_hash( array( 'post' => array( $post->ID, $post->post_modified_gmt, $post->post_status ), 'fields' => $fields, 'settings' => $native['settings'] ?? array() ) ), 'warnings' => array( 'target_binding_unavailable' ),
		)
	);
}

/** @return array<string,mixed>|WP_Error */
function wpagent_content_model_snapshot( string $provider, $identifier ) {
	if ( ! in_array( $provider, wpagent_content_model_providers(), true ) ) {
		return new WP_Error( 'wpagent_content_model_provider', __( 'Der Datenmodellanbieter wird nicht unterstützt.', 'wp-agent-companion' ), array( 'status' => 400 ) );
	}
	$identifier = wpagent_content_model_identifier( $provider, $identifier );
	if ( is_wp_error( $identifier ) ) {
		return $identifier;
	}
	$capabilities = wpagent_content_model_capabilities( $provider );
	if ( 'acf' === $provider ) return $capabilities['snapshot'] ? wpagent_content_model_snapshot_acf( $identifier, $capabilities ) : wpagent_content_model_finish_snapshot( wpagent_content_model_absent_snapshot( $provider, $identifier, $capabilities ) );
	if ( 'pods' === $provider ) return $capabilities['snapshot'] ? wpagent_content_model_snapshot_pods( $identifier, $capabilities ) : wpagent_content_model_finish_snapshot( wpagent_content_model_absent_snapshot( $provider, $identifier, $capabilities ) );
	if ( 'acpt-lite' === $provider ) return $capabilities['snapshot'] ? wpagent_content_model_snapshot_acpt( $identifier, $capabilities ) : wpagent_content_model_finish_snapshot( wpagent_content_model_absent_snapshot( $provider, $identifier, $capabilities ) );
	return wpagent_content_model_snapshot_meta_box( $identifier, $capabilities );
}
