<?php
// phpcs:disable Yoast.NamingConventions.NamespaceName.TooLong
// phpcs:disable Yoast.NamingConventions.NamespaceName.MaxExceeded
namespace Yoast\WP\SEO\Task_List\Infrastructure\Indexables;

use Yoast\WP\SEO\Dashboard\Application\Score_Groups\Readability_Score_Groups\Readability_Score_Groups_Repository;
use Yoast\WP\SEO\Dashboard\Application\Score_Groups\SEO_Score_Groups\SEO_Score_Groups_Repository;
use Yoast\WP\SEO\Repositories\Indexable_Repository;
use Yoast\WP\SEO\Task_List\Domain\Data\Content_Item_Score_Data;
use Yoast\WP\SEO\Task_List\Domain\Data\Meta_Description_Content_Item_Data;

/**
 * Collector that retrieves recent content items with their scores.
 */
class Recent_Content_Indexable_Collector {

	/**
	 * The indexable repository.
	 *
	 * @var Indexable_Repository
	 */
	private $indexable_repository;

	/**
	 * The SEO score groups repository.
	 *
	 * @var SEO_Score_Groups_Repository
	 */
	private $seo_score_groups_repository;

	/**
	 * The readability score groups repository.
	 *
	 * @var Readability_Score_Groups_Repository
	 */
	private $readability_score_groups_repository;

	/**
	 * The constructor.
	 *
	 * @param Indexable_Repository                $indexable_repository                The indexable repository.
	 * @param SEO_Score_Groups_Repository         $seo_score_groups_repository         The SEO score groups repository.
	 * @param Readability_Score_Groups_Repository $readability_score_groups_repository The readability score groups repository.
	 */
	public function __construct(
		Indexable_Repository $indexable_repository,
		SEO_Score_Groups_Repository $seo_score_groups_repository,
		Readability_Score_Groups_Repository $readability_score_groups_repository
	) {
		$this->indexable_repository                = $indexable_repository;
		$this->seo_score_groups_repository         = $seo_score_groups_repository;
		$this->readability_score_groups_repository = $readability_score_groups_repository;
	}

	/**
	 * Gets recent content items with SEO scores for the given post type.
	 *
	 * @param string   $post_type  The post type to query.
	 * @param string   $date_limit The date limit (content modified after this date).
	 * @param int|null $limit      Optional. Maximum number of items to retrieve.
	 *
	 * @return Content_Item_Score_Data[] Array of content item score data value objects.
	 */
	public function get_recent_content_with_seo_scores( string $post_type, string $date_limit, ?int $limit = null ): array {
		$raw_results = $this->indexable_repository->get_recent_posts_with_keywords_for_post_type(
			$post_type,
			$limit,
			$date_limit,
		);

		return $this->map_to_seo_score_data( $raw_results, $post_type );
	}

	/**
	 * Gets recent content items with readability scores for the given post type.
	 *
	 * @param string   $post_type  The post type to query.
	 * @param string   $date_limit The date limit (content modified after this date).
	 * @param int|null $limit      Optional. Maximum number of items to retrieve.
	 *
	 * @return Content_Item_Score_Data[] Array of content item score data value objects.
	 */
	public function get_recent_content_with_readability_scores( string $post_type, string $date_limit, ?int $limit = null ): array {
		$raw_results = $this->indexable_repository->get_recent_posts_with_readability_scores_for_post_type(
			$post_type,
			$limit,
			$date_limit,
		);

		return $this->map_to_readability_score_data( $raw_results, $post_type );
	}

	/**
	 * Gets recent content items for the meta descriptions task for the given post type.
	 *
	 * @param string   $post_type  The post type to query.
	 * @param string   $date_limit The date limit (content modified after this date).
	 * @param int|null $limit      Optional. Maximum number of items to retrieve.
	 *
	 * @return Meta_Description_Content_Item_Data[] Array of content item data value objects.
	 */
	public function get_recent_content_for_meta_descriptions( string $post_type, string $date_limit, ?int $limit = null ): array {
		$raw_results = $this->indexable_repository->get_recent_posts_for_post_type(
			$post_type,
			$limit,
			$date_limit,
		);

		if ( ! \is_array( $raw_results ) ) {
			return [];
		}

		$content_items = [];

		foreach ( $raw_results as $result ) {
			$content_items[] = new Meta_Description_Content_Item_Data(
				(int) $result['object_id'],
				$result['breadcrumb_title'],
				(string) $result['description'] !== '',
			);
		}

		return $content_items;
	}

	/**
	 * Maps raw database results to Content_Item_Score_Data value objects for SEO scores.
	 *
	 * @param array<array<string, string>> $raw_results The raw results from the repository.
	 * @param string                       $post_type   The post type.
	 *
	 * @return Content_Item_Score_Data[] Array of content item score data value objects.
	 */
	private function map_to_seo_score_data( array $raw_results, string $post_type ): array {
		$content_items = [];

		foreach ( $raw_results as $result ) {
			$seo_score_group = $this->seo_score_groups_repository->get_score_group( (int) $result['primary_focus_keyword_score'] );

			$content_items[] = new Content_Item_Score_Data(
				(int) $result['object_id'],
				$result['breadcrumb_title'],
				$seo_score_group->get_name(),
				$post_type,
			);
		}

		return $content_items;
	}

	/**
	 * Maps raw database results to Content_Item_Score_Data value objects for readability scores.
	 *
	 * @param array<array<string, string>> $raw_results The raw results from the repository.
	 * @param string                       $post_type   The post type.
	 *
	 * @return Content_Item_Score_Data[] Array of content item score data value objects.
	 */
	private function map_to_readability_score_data( array $raw_results, string $post_type ): array {
		$content_items = [];

		foreach ( $raw_results as $result ) {
			// @TODO: Instead of this inline quick fix, let's properly handle the case where readability_score is 0 in the repository method, and return 'bad' directly from there (SEO scores having a different logic might make this a bit harder).
			// Also read as: The refactoring of https://github.com/Yoast/wordpress-seo/pull/22947 was not quite right.
			if ( (int) $result['readability_score'] === 0 ) {
				$score_name = 'bad';
			}
			else {
				$readability_score_group = $this->readability_score_groups_repository->get_score_group( (int) $result['readability_score'] );
				$score_name              = $readability_score_group->get_name();
			}

			$content_items[] = new Content_Item_Score_Data(
				(int) $result['object_id'],
				$result['breadcrumb_title'],
				$score_name,
				$post_type,
			);
		}

		return $content_items;
	}
}
