<?php

namespace Limb_Chatbot\Includes\Services\Knowledge;

use Limb_Chatbot\Includes\Data_Objects\Dataset;
use Limb_Chatbot\Includes\Data_Objects\Setting;
use Limb_Chatbot\Includes\Exceptions\Error_Codes;
use Limb_Chatbot\Includes\Exceptions\Exception;
use Limb_Chatbot\Includes\Services\Helper;

/**
 * Centralized resolver/validator for post knowledge content configuration.
 *
 * This service is intentionally scoped to Dataset::SOURCE_POST flow.
 *
 * @since 1.0.0
 */
class Post_Content_Config_Service {

	/**
	 * Config key used in dataset generation job payload.
	 */
	public const CONFIG_KEY = 'content_config';

	/**
	 * Native fields config key.
	 */
	public const NATIVE_FIELDS_KEY = 'native_fields';

	/**
	 * Taxonomies config key (flat list of term IDs).
	 */
	public const TAXONOMIES_KEY = 'taxonomies';

	/**
	 * Validate job config shape for post-source datasets.
	 *
	 * @param  array  $config  Full job config.
	 *
	 * @return void
	 * @throws Exception
	 * @since 1.0.0
	 */
	public function validate_post_job_config( array $config ): void {
		$source = $config['source'] ?? null;
		$this->maybe_apply_autosync_setting( $config );
		if ( Dataset::SOURCE_POST !== $source ) {
			return;
		}

		$post_type = isset( $config['post_type'] ) ? sanitize_key( (string) $config['post_type'] ) : '';
		$this->assert_valid_post_type( $post_type );

		// This also validates payload shape and filters unknown/unavailable fields.
		$this->resolve_for_post_type( $post_type, $config );
	}

	/**
	 * Optionally overwrite autosync setting from job payload.
	 *
	 * If autosync is not provided, this method is a no-op.
	 *
	 * @param  array  $config
	 *
	 * @return void
	 */
	private function maybe_apply_autosync_setting( array $config ): void {
		if ( ! array_key_exists( 'autosync', $config ) ) {
			return;
		}

		$source   = $config['source'] ?? '';
		$autosync = ! empty( $config['autosync'] ) ? 1 : 0;

		if ( Dataset::SOURCE_POST === $source ) {
			$post_type = sanitize_key( (string) ( $config['post_type'] ?? '' ) );
			if ( '' === $post_type ) {
				return;
			}
			Setting::update(
				[ 'key' => 'lbaic.utilities.chatbot.auto_sync_for_post_' . $post_type ],
				[ 'value' => $autosync ]
			);

			return;
		}
	}

	/**
	 * Validate post type and throw domain exception when invalid.
	 *
	 * @param  string  $post_type  Post type slug.
	 *
	 * @return void
	 * @throws Exception
	 * @since 1.0.0
	 */
	private function assert_valid_post_type( string $post_type ): void {
		if ( '' === $post_type || ! get_post_type_object( $post_type ) ) {
			throw new Exception(
				Error_Codes::VALIDATION_INVALID_VALUE,
				__( 'Invalid post type for content configuration.', 'limb-chatbot' )
			);
		}
	}

	/**
	 * Resolve effective content config for a post type.
	 *
	 * Precedence:
	 * 1) Job payload `content_config` (if provided)
	 * 2) Group setting `lbaic.utilities.chatbot.content_config_for_{post_type}`
	 *
	 * @param  string  $post_type  Post type slug.
	 * @param  array  $job_config  Full job config.
	 *
	 * @return array
	 * @since 1.0.0
	 */
	public function resolve_for_post_type( string $post_type, array $job_config ): array {
		$post_type = sanitize_key( $post_type );
		$this->assert_valid_post_type( $post_type );

		$raw_config = array_key_exists( self::CONFIG_KEY, $job_config )
			? $job_config[ self::CONFIG_KEY ]
			: $this->get_setting_content_config( $post_type );

		return $this->normalize_and_filter_content_config( $post_type, $raw_config );
	}

	/**
	 * Read group-level content config setting for a post type.
	 *
	 * @param  string  $post_type
	 *
	 * @return mixed
	 * @since 1.0.0
	 */
	private function get_setting_content_config( string $post_type ) {
		$setting = Setting::find( 'lbaic.utilities.chatbot.content_config_for_' . $post_type );

		return $setting ? $setting->get_value() : [];
	}

	/**
	 * Normalize and filter content config by available fields.
	 *
	 * @param  string  $post_type  Post type slug.
	 * @param  mixed  $raw_config  Raw payload or setting value.
	 *
	 * @return array
	 * @throws Exception
	 * @since 1.0.0
	 */
	private function normalize_and_filter_content_config( string $post_type, $raw_config ): array {
		if ( null === $raw_config || false === $raw_config || '' === $raw_config ) {
			$raw_config = [];
		}

		if ( ! is_array( $raw_config ) ) {
			throw new Exception(
				Error_Codes::VALIDATION_INVALID_VALUE,
				__( 'content_config must be an object/array.', 'limb-chatbot' )
			);
		}

		$normalized = [
			self::NATIVE_FIELDS_KEY      => $this->normalize_field_list( $raw_config[ self::NATIVE_FIELDS_KEY ] ?? [] ),
			self::TAXONOMIES_KEY         => $this->normalize_term_ids( $raw_config[ self::TAXONOMIES_KEY ] ?? [] ),
		];

		$allowed_native = Helper::get_supported_native_fields( $post_type );
		$allowed_terms  = $this->get_supported_taxonomy_term_ids( $post_type );

		$normalized[ self::NATIVE_FIELDS_KEY ] = $this->filter_to_supported_fields(
			$normalized[ self::NATIVE_FIELDS_KEY ],
			$allowed_native
		);
		$normalized[ self::TAXONOMIES_KEY ]    = $this->filter_to_supported_term_ids(
			$normalized[ self::TAXONOMIES_KEY ],
			$allowed_terms
		);

		return $normalized;
	}

	/**
	 * Normalize unknown input to a clean list of field keys.
	 *
	 * @param  mixed  $raw_list
	 *
	 * @return array
	 * @throws Exception
	 * @since 1.0.0
	 */
	private function normalize_field_list( $raw_list ): array {
		if ( null === $raw_list || false === $raw_list || '' === $raw_list ) {
			return [];
		}

		if ( ! is_array( $raw_list ) ) {
			throw new Exception(
				Error_Codes::VALIDATION_INVALID_VALUE,
				__( 'Each content_config field list must be an array.', 'limb-chatbot' )
			);
		}

		$list = array_map(
			static function ( $item ) {
				return is_string( $item ) ? sanitize_key( $item ) : '';
			},
			$raw_list
		);

		$list = array_values(
			array_filter(
				array_unique( $list ),
				static function ( string $field_key ): bool {
					return '' !== $field_key;
				}
			)
		);

		return $list;
	}

	/**
	 * Normalize unknown input to a clean list of term IDs.
	 *
	 * @param  mixed  $raw_list
	 *
	 * @return int[]
	 * @throws Exception
	 * @since 1.0.0
	 */
	private function normalize_term_ids( $raw_list ): array {
		if ( null === $raw_list || false === $raw_list || '' === $raw_list ) {
			return [];
		}

		if ( ! is_array( $raw_list ) ) {
			throw new Exception(
				Error_Codes::VALIDATION_INVALID_VALUE,
				__( 'content_config.taxonomies must be an array of term IDs.', 'limb-chatbot' )
			);
		}

		$term_ids = array_map( 'absint', $raw_list );
		$term_ids = array_values( array_filter( array_unique( $term_ids ) ) );

		return $term_ids;
	}

	/**
	 * Supported taxonomy term IDs for a post type.
	 *
	 * @param  string  $post_type
	 *
	 * @return int[]
	 * @since 1.0.0
	 */
	private function get_supported_taxonomy_term_ids( string $post_type ): array {
		$taxonomy_names = get_object_taxonomies( $post_type, 'names' );

		if ( empty( $taxonomy_names ) ) {
			return [];
		}

		$terms = get_terms( [
			'taxonomy'   => $taxonomy_names,
			'hide_empty' => false,
			'fields'     => 'ids',
		] );

		if ( is_wp_error( $terms ) || ! is_array( $terms ) ) {
			return [];
		}

		$term_ids = array_map( 'absint', $terms );
		$term_ids = array_values( array_filter( array_unique( $term_ids ) ) );

		return $term_ids;
	}

	/**
	 * Keep selected fields that are currently supported.
	 *
	 * @param  array  $selected
	 * @param  array  $supported
	 *
	 * @return array
	 * @since 1.0.0
	 */
	private function filter_to_supported_fields( array $selected, array $supported ): array {
		if ( empty( $selected ) || empty( $supported ) ) {
			return [];
		}

		$selected = array_values(
			array_filter(
				$selected,
				static function ( $field_key ) use ( $supported ) {
					return in_array( $field_key, $supported, true );
				}
			)
		);

		return $selected;
	}

	/**
	 * Keep selected term IDs that are supported by post type taxonomies.
	 *
	 * @param  int[]  $selected
	 * @param  int[]  $supported
	 *
	 * @return int[]
	 * @since 1.0.0
	 */
	private function filter_to_supported_term_ids( array $selected, array $supported ): array {
		if ( empty( $selected ) || empty( $supported ) ) {
			return [];
		}

		$selected = array_values(
			array_filter(
				$selected,
				static function ( int $term_id ) use ( $supported ) {
					return in_array( $term_id, $supported, true );
				}
			)
		);

		return $selected;
	}
}

