<?php

namespace Limb_Chatbot\Includes\Services\Automations;

use Limb_Chatbot\Includes\Data_Objects\AI_Model;
use Limb_Chatbot\Includes\Data_Objects\Chatbot;
use Limb_Chatbot\Includes\Data_Objects\Dataset;
use Limb_Chatbot\Includes\Data_Objects\Job;
use Limb_Chatbot\Includes\Data_Objects\Setting;
use Limb_Chatbot\Includes\Services\Dataset_Service;
use Limb_Chatbot\Includes\Services\Helper;
use Limb_Chatbot\Includes\Services\Jobs\Handlers\Dataset_Generating;
use Limb_Chatbot\Includes\Services\Knowledge\Dataset_Builders\Informational_Dataset_Builder;
use Limb_Chatbot\Includes\Services\Knowledge\Post_Content_Config_Service;
use WP_Post;

/**
 * Handles dataset creation when a post is published.
 *
 * Creates both informational and actionable datasets for newly published posts
 * using the official dataset builders.
 *
 * @since 1.0.11
 */
class Post_Creation_Handler {
	/**
	 * Global role that allows unrestricted access.
	 */
	private const ROLE_EVERYONE = 'everyone';


	/**
	 * Dataset service.
	 *
	 * @var Dataset_Service
	 * @since 1.0.11
	 */
	private Dataset_Service $dataset_service;

	/**
	 * Constructor.
	 *
	 * @param  Dataset_Service  $dataset_service  Dataset service instance.
	 *
	 * @since 1.0.11
	 */
	public function __construct( Dataset_Service $dataset_service ) {
		$this->dataset_service = $dataset_service;
	}

	/**
	 * Handle post creation by creating datasets.
	 *
	 * When a post is published and auto-sync is enabled, creates both informational
	 * and actionable datasets using the official builders.
	 *
	 * @param  int  $post_id  Post ID.
	 * @param  WP_Post  $post  The post object.
	 *
	 * @return void
	 * @since 1.0.11
	 */
	public function handle( int $post_id, WP_Post $post ): void {
		// Ignore auto-saves and revisions
		if ( wp_is_post_autosave( $post_id ) || wp_is_post_revision( $post_id ) ) {
			return;
		}

		$chatbot = Chatbot::make();
		try {
			$auto_sync = $chatbot->get_parameter( "auto_sync_for_post_{$post->post_type}" );
			if ( ! $auto_sync ) {
				return;
			}

			// Create both dataset types using the official builders
			$this->create_informational_dataset( $post, $chatbot );
		} catch ( \Exception $e ) {
			Helper::log( $e );
		}
	}

	/**
	 * Create informational and actionable datasets using official builders.
	 *
	 * @param  WP_Post  $post  The published post object.
	 * @param  Chatbot  $chatbot  The chatbot instance.
	 *
	 * @return void
	 * @since 1.0.11
	 */
	private function create_informational_dataset( WP_Post $post, Chatbot $chatbot ): void {
		try {
			// Prepare job with KB configuration
			$informational_job = $this->prepare_job( $post, $chatbot, Job::SUB_TYPE_INFORMATIONAL_DATASET_GENERATION );

			if ( $informational_job ) {
				// Prepare dataset data
				$data = [
					'source_type'     => Dataset::SOURCE_POST,
					'source_sub_type' => $post->post_type,
					'source'          => $post->ID,
				];

				// Create informational dataset using official builder
				$informational_builder = new Informational_Dataset_Builder();
				$informational_dataset = $informational_builder->build( $data, $informational_job );

				if ( $informational_dataset instanceof Dataset ) {
					$this->dataset_service->regenerate_dataset_entries( $informational_dataset );
				}
			}
		} catch ( \Exception $e ) {
			Helper::log( $e );
		}
	}

	/**
	 * Prepare a Job object with configuration from chatbot KB settings.
	 *
	 * @param  WP_Post  $post  The post being indexed.
	 * @param  Chatbot  $chatbot  The chatbot with KB settings.
	 *
	 * @return Job|null The prepared job or null if configuration is incomplete.
	 * @since 1.0.11
	 */
	private function prepare_job( WP_Post $post, Chatbot $chatbot, $sub_type ): ?Job {
		try {
			// Get KB settings from chatbot
			$kb_ai_model_id       = $chatbot->get_parameter( 'kb_ai_model_id' );
			$kb_config_id         = $chatbot->get_parameter( 'kb_config_id' );
			$kb_vector_index_type = $chatbot->get_parameter( 'kb_vector_index_type' ) ?? 'local';
			$kb_vector_config_id  = $chatbot->get_parameter( 'kb_vector_config_id' );
			$dimension            = $chatbot->get_parameter( 'kb_dimension' ) ?? 1536;


			// Apply default fallbacks if settings missing
			if ( empty( $kb_ai_model_id ) || empty( $kb_config_id ) ) {
				$config      = $chatbot->get_config();
				$ai_provider = $config ? $config->get_related_to_instance() : null;

				if ( ! $ai_provider ) {
					return null;
				}

				// Get default embedding model from provider
				if ( empty( $kb_ai_model_id ) ) {
					$default_model  = $ai_provider->get_default_embedding_model();
					$kb_ai_model_id = $default_model instanceof AI_Model ? $default_model->get_id() : null;
				}

				// Use main config if KB config not set
				if ( empty( $kb_config_id ) ) {
					$kb_config_id = $config->get_id();
				}
			}

			if ( empty( $kb_ai_model_id ) || empty( $kb_config_id ) ) {
				return null;
			}

			$user_roles     = $this->resolve_user_roles_from_settings( $post->post_type );
			$content_config = $this->resolve_content_config_from_settings( $post->post_type );

			// Create Job with configuration
			$job = new Job();
			$job->set_type( Job::TYPE_DATASET_GENERATING );
			$job->set_chatbot_uuid( $chatbot->uuid ?? Job::CHATBOT_DEFAULT );
			$job->set_sub_type( $sub_type );

			// Build configuration for builders
			$config = [
				'source'                          => Dataset::SOURCE_POST,
				'indexing_ai_model_id'            => $kb_ai_model_id,
				'indexing_config_id'              => $kb_config_id,
				'indexing_vector_index_type'      => $kb_vector_index_type,
				'indexing_vector_index_config_id' => $kb_vector_config_id,
				'indexing_dimension'              => $dimension,
				'user_roles'                      => $user_roles,
				'content_config'                  => $content_config,
			];
			$config = Dataset_Generating::resolve_informational_vector_index_config(
				$config,
				$chatbot->uuid ?? null
			);
			$job->set_config( $config );

			return $job;
		} catch ( \Exception $e ) {
			Helper::log( $e );

			return null;
		}
	}

	/**
	 * Resolve access roles from group settings for a post type.
	 *
	 * Defaults to `everyone` when setting is missing or empty.
	 *
	 * @param  string  $post_type  Post type slug.
	 *
	 * @return string[]
	 */
	private function resolve_user_roles_from_settings( string $post_type ): array {
		$group_slug  = sprintf( '%s:%s', Dataset::SOURCE_POST, rawurlencode( $post_type ) );
		$setting_key = 'lbaic.utilities.chatbot.user_roles_for_' . $group_slug;
		$setting     = Setting::find( $setting_key );
		$roles       = $setting ? $setting->get_value() : [];
		$roles       = $this->normalize_roles( $roles );

		if ( empty( $roles ) ) {
			return [ self::ROLE_EVERYONE ];
		}

		return $roles;
	}

	/**
	 * Normalize role values to a unique list.
	 *
	 * @param  mixed  $roles
	 *
	 * @return string[]
	 */
	private function normalize_roles( $roles ): array {
		if ( is_string( $roles ) ) {
			$decoded = json_decode( $roles, true );
			if ( is_array( $decoded ) ) {
				$roles = $decoded;
			} else {
				$roles = str_contains( $roles, ',' ) ? array_map( 'trim', explode( ',', $roles ) ) : [ $roles ];
			}
		}
		if ( ! is_array( $roles ) ) {
			return [];
		}

		$roles = array_map(
			static function ( $role ): string {
				return is_string( $role ) ? sanitize_key( $role ) : '';
			},
			$roles
		);

		return array_values( array_filter( array_unique( $roles ) ) );
	}

	/**
	 * Resolve content config from post type settings.
	 *
	 * Defaults native fields to all supported WP native fields
	 * when setting is missing or native fields are empty.
	 *
	 * @param  string  $post_type  Post type slug.
	 *
	 * @return array
	 */
	private function resolve_content_config_from_settings( string $post_type ): array {
		$setting_key = 'lbaic.utilities.chatbot.content_config_for_' . $post_type;
		$setting     = Setting::find( $setting_key );
		$raw_config  = $setting ? $setting->get_value() : [];

		if ( is_string( $raw_config ) ) {
			$decoded    = json_decode( $raw_config, true );
			$raw_config = is_array( $decoded ) ? $decoded : [];
		}
		if ( ! is_array( $raw_config ) ) {
			$raw_config = [];
		}

		$content_config = [
			Post_Content_Config_Service::NATIVE_FIELDS_KEY => $this->normalize_string_list( $raw_config[ Post_Content_Config_Service::NATIVE_FIELDS_KEY ] ?? [] ),
			Post_Content_Config_Service::TAXONOMIES_KEY    => $this->normalize_term_ids( $raw_config[ Post_Content_Config_Service::TAXONOMIES_KEY ] ?? [] ),
		];

		if ( empty( $content_config[ Post_Content_Config_Service::NATIVE_FIELDS_KEY ] ) ) {
			$content_config[ Post_Content_Config_Service::NATIVE_FIELDS_KEY ] = Helper::get_supported_native_fields( $post_type );
		}

		return $content_config;
	}

	/**
	 * Normalize mixed input to a unique string list.
	 *
	 * @param  mixed  $values
	 *
	 * @return string[]
	 */
	private function normalize_string_list( $values ): array {
		if ( is_string( $values ) ) {
			$decoded = json_decode( $values, true );
			$values  = is_array( $decoded ) ? $decoded : [];
		}
		if ( ! is_array( $values ) ) {
			return [];
		}

		$values = array_map(
			static function ( $value ): string {
				return is_string( $value ) ? sanitize_key( $value ) : '';
			},
			$values
		);

		return array_values( array_filter( array_unique( $values ) ) );
	}

	/**
	 * Normalize taxonomy term IDs from mixed input.
	 *
	 * @param  mixed  $term_ids
	 *
	 * @return int[]
	 */
	private function normalize_term_ids( $term_ids ): array {
		if ( is_string( $term_ids ) ) {
			$decoded  = json_decode( $term_ids, true );
			$term_ids = is_array( $decoded ) ? $decoded : [];
		}
		if ( ! is_array( $term_ids ) ) {
			return [];
		}

		$term_ids = array_map( 'absint', $term_ids );

		return array_values( array_filter( array_unique( $term_ids ) ) );
	}
}
