<?php

namespace Limb_Chatbot\Includes\Services\Knowledge\Source_Object_Fetchers;

use Limb_Chatbot\Includes\Interfaces\Source_Fetcher_Interface;


/**
 * Class Posts_Source_Fetcher
 *
 * Fetches dataset entries from WordPress posts based on
 * plugin configuration and pagination parameters.
 *
 * Implements the Source_Fetcher_Interface contract to provide
 * a consistent data structure for dataset generation.
 *
 * @package Limb_Chatbot\Includes\Services\Knowledge\Source_Object_Fetchers
 * @since 1.0.0
 */
class Posts_Source_Fetcher implements Source_Fetcher_Interface {
	/**
	 * Supported meta operators for filter-based retrieval.
	 *
	 * @var array<string, string>
	 * @since 1.0.0
	 */
	private const META_FILTER_OPERATORS = [
		'equal'         => '=',
		'not_equal'     => '!=',
		'contains'      => 'LIKE',
		'greather_than' => '>',
		'greater_than'  => '>',
		'less_then'     => '<',
		'less_than'     => '<',
	];

	/**
	 * Fetch dataset entries from WordPress posts.
	 *
	 * Retrieves post IDs according to plugin settings and
	 * request-specific pagination parameters, then maps them into
	 * structured dataset entries.
	 *
	 * @param  array  $item  Arguments that control fetching behavior.
	 *
	 * @return array[] Structured dataset entries.
	 * @since 1.0.0
	 */
	public function fetch( array $item, array $config ): array {
		$post_type = sanitize_key( (string) $config['post_type'] );
		$has_limit = array_key_exists( 'limit', $config ) && null !== $config['limit'] && '' !== $config['limit'];

		$args = [
			'post_type'      => $post_type,
			'paged'          => $item['paged'],
			'posts_per_page' => $item['per_page'],
			'post_status'    => 'publish',
			'fields'         => 'ids',
			'orderby'        => 'date',
			'order'          => 'DESC',
		];

		if ( $has_limit && is_array( $config['limit'] ) ) {
			$args['post__in'] = array_values( array_filter( array_map( 'absint', $config['limit'] ) ) );
		} elseif ( ! $has_limit ) {
			$filters = isset( $config['filters'] ) && is_array( $config['filters'] ) ? $config['filters'] : [];
			$args    = array_merge( $args, $this->build_filtered_query_args( $post_type, $filters ) );
		}

		$ids    = get_posts( $args );
		$result = [];
		foreach ( $ids as $id ) {
			$result[] = [
				'source_type'     => 'post',
				'source'          => $id,
				'source_sub_type' => $post_type,
			];
		}

		return $result;
	}

	/**
	 * Build query args from filters payload.
	 *
	 * @param  string  $post_type
	 * @param  array  $filters
	 *
	 * @return array
	 * @since 1.0.0
	 */
	private function build_filtered_query_args( string $post_type, array $filters ): array {
		$filters = $this->normalize_filters_payload( $filters );

		$args = [
			'post_type'   => $post_type,
			'post_status' => 'publish',
		];

		$tax_query = $this->build_tax_query( $filters['taxonomies'] ?? [] );
		if ( ! empty( $tax_query ) ) {
			$args['tax_query'] = $tax_query;
		}

		$meta_query = $this->build_meta_query( $filters['meta_fields'] ?? [] );
		if ( ! empty( $meta_query ) ) {
			$args['meta_query'] = $meta_query;
		}

		$author_ids = $this->sanitize_author_ids( $filters['author'] ?? [] );
		if ( ! empty( $author_ids ) ) {
			$args['author__in'] = $author_ids;
		}

		return $args;
	}

	/**
	 * Normalize filters payload from rule-list shape to grouped query shape.
	 *
	 * @param  array  $filters
	 *
	 * @return array
	 * @since 1.0.0
	 */
	private function normalize_filters_payload( array $filters ): array {
		if ( isset( $filters['taxonomies'] ) || isset( $filters['meta_fields'] ) || isset( $filters['author'] ) ) {
			return $filters;
		}

		$normalized = [
			'taxonomies' => [],
			'meta_fields' => [],
			'author' => [],
		];

		foreach ( $filters as $filter ) {
			if ( ! is_array( $filter ) || empty( $filter['type'] ) ) {
				continue;
			}

			$type = sanitize_key( (string) $filter['type'] );

			if ( 'taxonomy' === $type ) {
				$taxonomy = sanitize_key( (string) ( $filter['taxonomy'] ?? '' ) );
				$terms    = is_array( $filter['terms'] ?? null ) ? $filter['terms'] : [];
				if ( '' !== $taxonomy && ! empty( $terms ) ) {
					$normalized['taxonomies'][ $taxonomy ] = $terms;
				}
				continue;
			}

			if ( 'meta_field' === $type ) {
				$meta_key = sanitize_key( (string) ( $filter['meta_field'] ?? '' ) );
				if ( '' === $meta_key ) {
					continue;
				}
				$normalized['meta_fields'][ $meta_key ] = [
					'operator' => sanitize_key( (string) ( $filter['operator'] ?? 'equal' ) ),
					'value'    => $filter['meta_value'] ?? null,
				];
				continue;
			}

			if ( 'authors' === $type ) {
				$authors = is_array( $filter['authors'] ?? null ) ? $filter['authors'] : [];
				if ( ! empty( $authors ) ) {
					$normalized['author'] = array_values( array_unique( array_merge( $normalized['author'], $authors ) ) );
				}
			}
		}

		return $normalized;
	}

	/**
	 * Build WP tax_query from payload.
	 *
	 * @param  mixed  $raw_taxonomies
	 *
	 * @return array
	 * @since 1.0.0
	 */
	private function build_tax_query( $raw_taxonomies ): array {
		if ( ! is_array( $raw_taxonomies ) || empty( $raw_taxonomies ) ) {
			return [];
		}

		$tax_query = [ 'relation' => 'AND' ];

		foreach ( $raw_taxonomies as $taxonomy_name => $terms ) {
			$taxonomy_name = sanitize_key( (string) $taxonomy_name );

			if ( '' === $taxonomy_name || ! taxonomy_exists( $taxonomy_name ) || ! is_array( $terms ) || empty( $terms ) ) {
				continue;
			}

			$sanitized_terms = array_values( array_filter( array_map( static function ( $term ) {
				if ( is_numeric( $term ) ) {
					return absint( $term );
				}

				if ( is_string( $term ) ) {
					$slug = sanitize_title( $term );

					return '' !== $slug ? $slug : null;
				}

				return null;
			}, $terms ) ) );

			if ( empty( $sanitized_terms ) ) {
				continue;
			}

			$all_are_ids = count( array_filter( $sanitized_terms, 'is_int' ) ) === count( $sanitized_terms );

			$tax_query[] = [
				'taxonomy' => $taxonomy_name,
				'field'    => $all_are_ids ? 'term_id' : 'slug',
				'terms'    => $sanitized_terms,
				'operator' => 'IN',
			];
		}

		return count( $tax_query ) > 1 ? $tax_query : [];
	}

	/**
	 * Build WP meta_query from payload.
	 *
	 * @param  mixed  $raw_meta_fields
	 *
	 * @return array
	 * @since 1.0.0
	 */
	private function build_meta_query( $raw_meta_fields ): array {
		if ( ! is_array( $raw_meta_fields ) || empty( $raw_meta_fields ) ) {
			return [];
		}

		$meta_query = [ 'relation' => 'AND' ];

		foreach ( $raw_meta_fields as $meta_key => $meta_rule ) {
			$meta_key = sanitize_key( (string) $meta_key );
			if ( '' === $meta_key || ! is_array( $meta_rule ) ) {
				continue;
			}

			$operator_key = isset( $meta_rule['operator'] ) ? sanitize_key( (string) $meta_rule['operator'] ) : 'equal';
			$compare      = self::META_FILTER_OPERATORS[ $operator_key ] ?? self::META_FILTER_OPERATORS['equal'];
			$value        = $meta_rule['value'] ?? null;

			if ( null === $value ) {
				continue;
			}

			if ( 'LIKE' === $compare ) {
				$value = '%' . sanitize_text_field( (string) $value ) . '%';
			}

			$meta_query[] = [
				'key'     => $meta_key,
				'value'   => $this->sanitize_meta_value( $value ),
				'compare' => $compare,
			];
		}

		return count( $meta_query ) > 1 ? $meta_query : [];
	}

	/**
	 * Normalize author filter values to integer IDs.
	 *
	 * @param  mixed  $raw_authors
	 *
	 * @return int[]
	 * @since 1.0.0
	 */
	private function sanitize_author_ids( $raw_authors ): array {
		if ( ! is_array( $raw_authors ) || empty( $raw_authors ) ) {
			return [];
		}

		$author_ids = array_map( 'absint', $raw_authors );
		$author_ids = array_filter( array_unique( $author_ids ) );

		return array_values( $author_ids );
	}

	/**
	 * Sanitize meta value while preserving numeric comparisons.
	 *
	 * @param  mixed  $value
	 *
	 * @return mixed
	 * @since 1.0.0
	 */
	private function sanitize_meta_value( $value ) {
		if ( is_numeric( $value ) ) {
			return strpos( (string) $value, '.' ) !== false ? (float) $value : (int) $value;
		}

		if ( is_bool( $value ) ) {
			return $value ? '1' : '0';
		}

		return sanitize_text_field( (string) $value );
	}
}
