<?php

namespace Limb_Chatbot\Includes\Repositories;

use Limb_Chatbot\Includes\Data_Objects\AI_Model;
use Limb_Chatbot\Includes\Data_Objects\Dataset;
use Limb_Chatbot\Includes\Data_Objects\Dataset_Entry;
use Limb_Chatbot\Includes\Data_Objects\Dataset_Meta;
use Limb_Chatbot\Includes\Services\Collection;
use Limb_Chatbot\Includes\Services\Data_Object_Collection;


/**
 * Repository for managing Dataset records.
 *
 * Provides methods to retrieve datasets with search and pagination support.
 *
 * @since 1.0.0
 */
class Dataset_Repository {

	/**
	 * Shared filter pipeline: same transformations as REST list, returns full matching ID list (before pagination).
	 *
	 * @param array $params Query params (may include page/per_page/include for REST).
	 *
	 * @return array{datasets: mixed, ids: array<int|string>}
	 */
	private function run_dataset_eligibility_pipeline( array $params ): array {
		$has_group_filter = false;
		if ( isset( $params['item'] ) && '' !== (string) $params['item'] ) {
			$has_group_filter = true;
			$group_filter     = $this->parse_group_item_filter( (string) $params['item'] );
			unset( $params['item'] );
			if ( ! empty( $group_filter['source_type'] ) ) {
				$params['source_type'] = $group_filter['source_type'];
			}
			if ( array_key_exists( 'source_sub_type', $group_filter ) ) {
				$params['source_sub_type'] = $group_filter['source_sub_type'];
			} else {
				unset( $params['source_sub_type'] );
			}
		}
		if ( isset( $params['job_id'] ) ) {
			$metas = Dataset_Meta::where( [ 'meta_key' => 'job_id', 'meta_value' => $params['job_id'] ], - 1, - 1 );
			if ( $metas->is_empty() ) {
				$job_ids = [];
			} else {
				$job_ids = $metas->pluck( 'dataset_id' );
			}
			unset( $params['job_id'] );
		}
		if ( isset( $params['slug'] ) && '' !== (string) $params['slug'] ) {
			$has_group_filter = true;
			$group_filter     = $this->parse_group_item_filter( (string) $params['slug'] );
			unset( $params['slug'] );
			if ( ! empty( $group_filter['source_type'] ) ) {
				$params['source_type'] = $group_filter['source_type'];
			}
			if ( array_key_exists( 'source_sub_type', $group_filter ) ) {
				$params['source_sub_type'] = $group_filter['source_sub_type'];
			} else {
				unset( $params['source_sub_type'] );
			}
		}
		if ( $has_group_filter ) {
			$params['type'] = Dataset::TYPE_INFORMATIONAL_KNOWLEDGE;
		}
		if ( ! empty( $params['search'] ) && ! empty( $params['search_fields'] ) ) {
			foreach ( $params['search_fields'] as $field ) {
				$params["{$field}LIKE"] = "%{$params['search']}%";
			}
		}
		if ( isset( $params['synced'] ) ) {
			$dataset_metas = Dataset_Meta::where( [ 'meta_key' => 'synced', 'meta_value' => $params['synced'] ] );
			$synced_ids    = $dataset_metas->pluck( 'dataset_id' );
			unset( $params['synced'] );
		}
		if ( isset( $params['ai_provider_id'] ) ) {
			$model_ids       = AI_Model::where( [ 'ai_provider_id' => $params['ai_provider_id'] ] )->pluck( 'id' );
			$dataset_metas   = Dataset_Meta::where( [ 'meta_key' => 'index_ai_model_id', 'meta_value' => $model_ids ] );
			$ai_provider_ids = $dataset_metas->pluck( 'dataset_id' );
			unset( $params['ai_provider_id'] );
		}
		if ( isset( $params['vector_index_type'] ) ) {
			$dataset_metas         = Dataset_Meta::where( [
				'meta_key'   => 'vector_index_type',
				'meta_value' => $params['vector_index_type'],
			] );
			$vector_index_type_ids = $dataset_metas->pluck( 'dataset_id' );
			unset( $params['vector_index_type'] );
		}

		$datasets = Dataset::where( $params, - 1, - 1, $params['orderby'] ?? 'id', $params['order'] ?? 'DESC' );
		$new_ids  = $datasets->pluck( 'id' );
		if ( isset( $synced_ids ) ) {
			$new_ids = array_intersect( $new_ids, $synced_ids );
		}
		if ( isset( $ai_provider_ids ) ) {
			$new_ids = array_intersect( $new_ids, $ai_provider_ids );
		}
		if ( isset( $vector_index_type_ids ) ) {
			$new_ids = array_intersect( $new_ids, $vector_index_type_ids );
		}
		if ( isset( $job_ids ) ) {
			$new_ids = array_intersect( $new_ids, $job_ids );
		}

		return [
			'datasets' => $datasets,
			'ids'      => $new_ids,
		];
	}

	/**
	 * Retrieves a list of datasets based on the given parameters.
	 *
	 * Supports advanced filtering including:
	 * - Search by name/title
	 * - Filter by source type
	 * - Filter by synced status
	 * - Filter by AI provider
	 * - Filter by storage (vector_index_type)
	 *
	 * @param array $params {
	 *     Optional. Parameters for filtering and pagination.
	 *
	 * @type string $search Search keyword for name/title.
	 * @type string $source_type Filter by source type (post, term, file, url, text, cpt, q_a).
	 * @type int|bool $synced Filter by synced status (0/1 or false/true).
	 * @type string $ai_provider_id Filter by AI provider ID.
	 * @type string $vector_index_type Filter by vector index type (storage type).
	 * @type int $per_page Number of results per page. Default 10.
	 * @type int $page Current page number. Default 1.
	 * @type string $orderby Field to order by. Default 'id'.
	 * @type string $order Sort order direction. Accepts 'ASC' or 'DESC'. Default 'DESC'.
	 * }
	 *
	 * @return Data_Object_Collection Dataset objects with total count preserved.
	 * @since 1.0.0
	 *
	 */
	public function get_items( $params ) {
		if ( ! is_array( $params ) ) {
			$params = [];
		}

		$result   = $this->run_dataset_eligibility_pipeline( $params );
		$datasets = $result['datasets'];
		$new_ids  = $result['ids'];
		$per_page = $params['per_page'] ?? 10;
		$page     = $params['page'] ?? 1;
		$count    = count( $new_ids );
		$new_ids  = array_slice( $new_ids, ( $page - 1 ) * $per_page, $per_page );

		$datasets = $datasets->filter( static function ( Dataset $dataset ) use ( $new_ids ) {
			return in_array( $dataset->get_id(), $new_ids, true );
		} );
		$datasets->set_total( $count );

		return $datasets->with( [ 'ai_provider_id', 'dimension' ] );
	}

	/**
	 * Dataset IDs matching {@see get_items()} rules, without pagination (full filtered list).
	 *
	 * Used when jobs send `dataset_ids: all` plus REST-equivalent `filters`.
	 *
	 * @param array $params Same shape as REST query params (page/per_page ignored).
	 *
	 * @return int[]
	 * @since 1.1.0
	 */
	public function get_matching_dataset_ids( array $params ): array {
		unset( $params['include'], $params['page'], $params['per_page'] );

		$result = $this->run_dataset_eligibility_pipeline( $params );

		return array_map( 'intval', array_values( $result['ids'] ) );
	}

	private function parse_group_item_filter( string $item ): array {
		$item = trim( $item );
		if ( '' === $item ) {
			return [];
		}
		if ( in_array( $item, [ 'post', 'cpt', 'url', 'term', 'q_a' ], true ) ) {
			return [ 'source_type' => $item ];
		}
		if ( 'media' === $item ) {
			return [ 'source_type' => Dataset::SOURCE_FILE ];
		}
		if ( 'texts' === $item ) {
			return [ 'source_type' => Dataset::SOURCE_TEXT ];
		}
		if ( preg_match( '/^(post|cpt|url|term|q_a):(.+)$/', $item, $matches ) ) {
			return [
				'source_type'     => $matches[1],
				'source_sub_type' => rawurldecode( $matches[2] ),
			];
		}
		if ( preg_match( '/^(post|cpt|url|term|q_a)-(.+)$/', $item, $matches ) ) {
			return [
				'source_type'     => $matches[1],
				'source_sub_type' => rawurldecode( $matches[2] ),
			];
		}

		return [];
	}

	/**
	 * Deletes a dataset along with its entries and metadata.
	 *
	 * This method first checks if the dataset is currently being processed in either
	 * the actual or chunk import processes. If the dataset is active in a process,
	 * those processes are canceled before deletion. Afterward, the dataset itself,
	 * its entries, and its metadata are deleted.
	 *
	 * @param Dataset $dataset The dataset object to be deleted.
	 *
	 * @return bool True if the dataset was successfully deleted, false otherwise.
	 * @since 1.0.0
	 *
	 */
	public function delete( Dataset $dataset ) {
		$dataset_id = $dataset->get_id();
		if ( $deleted = Dataset::delete( [ 'id' => $dataset_id ] ) ) {
			Dataset_Entry::delete( [ 'dataset_id' => $dataset_id ] );
			Dataset_Meta::delete( [ 'dataset_id' => $dataset_id ] );
		}

		return $deleted;
	}

	/**
	 * Retrieves the first dataset matching the given parameters.
	 *
	 * Optionally, related data can be eager-loaded by specifying it in the 'include' key
	 * of the $params array.
	 *
	 * @param array $params Associative array of conditions for querying datasets.
	 *                      Example: ['id' => 123, 'status' => 'active', 'include' => ['entries', 'meta']]
	 *
	 * @return Dataset|null The first dataset matching the criteria, or null if none found.
	 * @since 1.0.0
	 *
	 */
	public function get_item( array $params ) {
		$items = Dataset::where( $params );
		if ( ! empty( $params['include'] ) ) {
			$items = $items->with( $params['include'] );
		}

		return $items->first();
	}

}