<?php

namespace Yoast\WP\SEO\Repositories;

use Yoast\WP\Lib\Model;
use Yoast\WP\Lib\ORM;
use Yoast\WP\SEO\Models\SEO_Links;

/**
 * Class SEO_Links_Repository.
 */
class SEO_Links_Repository {

	/**
	 * Starts a query for this repository.
	 *
	 * @return ORM
	 */
	public function query() {
		return Model::of_type( 'SEO_Links' );
	}

	/**
	 * Finds all SEO Links by post ID.
	 *
	 * @param int $post_id The post ID.
	 *
	 * @return SEO_Links[] The SEO Links.
	 */
	public function find_all_by_post_id( $post_id ) {
		return $this->query()
			->where( 'post_id', $post_id )
			->find_many();
	}

	/**
	 * Finds all SEO Links by indexable ID.
	 *
	 * @param int $indexable_id The indexable ID.
	 *
	 * @return SEO_Links[] The SEO Links.
	 */
	public function find_all_by_indexable_id( $indexable_id ) {
		return $this->query()
			->where( 'indexable_id', $indexable_id )
			->find_many();
	}

	/**
	 * Retrieves an SEO Link by url.
	 *
	 * @param string $url The SEO Link's url.
	 *
	 * @return SEO_Links|false The SEO Link, or false if none found.
	 */
	public function find_one_by_url( $url ) {
		return $this->query()
			->select( 'target_post_id' )
			->where( 'url', $url )
			->find_one();
	}

	/**
	 * Retrieves all SEO Links by target post ID.
	 *
	 * @param string $target_post_id The SEO Link's target post ID.
	 *
	 * @return SEO_Links[] The SEO Links.
	 */
	public function find_all_by_target_post_id( $target_post_id ) {
		return $this->query()
			->where( 'target_post_id', $target_post_id )
			->find_many();
	}

	/**
	 * Updates the ID of the target indexable of a link.
	 *
	 * @param int $link_id             The ID of the link to be updated.
	 * @param int $target_indexable_id The ID of the target indexable.
	 *
	 * @return bool Whether or not the update was succeful.
	 */
	public function update_target_indexable_id( $link_id, $target_indexable_id ) {
		return (bool) $this->query()
			->set( 'target_indexable_id', $target_indexable_id )
			->where( 'id', $link_id )
			->update_many();
	}

	/**
	 * Clears all SEO Links by post ID.
	 *
	 * @param int $post_id The post ID.
	 *
	 * @return bool Whether or not the delete was successful.
	 */
	public function delete_all_by_post_id( $post_id ) {
		return $this->query()
			->where( 'post_id', $post_id )
			->delete_many();
	}

	/**
	 * Clears all SEO Links by post ID where the indexable id is null.
	 *
	 * @param int $post_id The post ID.
	 *
	 * @return bool Whether or not the delete was successful.
	 */
	public function delete_all_by_post_id_where_indexable_id_null( $post_id ) {
		return $this->query()
			->where( 'post_id', $post_id )
			->where_null( 'indexable_id' )
			->delete_many();
	}

	/**
	 * Clears all SEO Links by indexable ID.
	 *
	 * @param int $indexable_id The indexable ID.
	 *
	 * @return bool Whether or not the delete was successful.
	 */
	public function delete_all_by_indexable_id( $indexable_id ) {
		return $this->query()
			->where( 'indexable_id', $indexable_id )
			->delete_many();
	}

	/**
	 * Returns incoming link counts for a number of posts.
	 *
	 * @param array $post_ids The post IDs.
	 *
	 * @return array An array of associative arrays, each containing a post id and incoming property.
	 */
	public function get_incoming_link_counts_for_post_ids( $post_ids ) {
		return $this->query()
			->select_expr( 'COUNT( id )', 'incoming' )
			->select( 'target_post_id', 'post_id' )
			->where_in( 'target_post_id', $post_ids )
			->group_by( 'target_post_id' )
			->find_array();
	}

	/**
	 * Returns incoming link counts for a number of indexables.
	 *
	 * @param array $indexable_ids The indexable IDs.
	 *
	 * @return array An array of associative arrays, each containing a indexable id and incoming property.
	 */
	public function get_incoming_link_counts_for_indexable_ids( $indexable_ids ) {
		if ( empty( $indexable_ids ) ) {
			return [];
		}

		// This query only returns ID's with an incoming count > 0. We need to restore any ID's with 0 incoming links later.
		$indexable_counts = $this->query()
			->select_expr( 'COUNT( id )', 'incoming' )
			->select( 'target_indexable_id' )
			->where_in( 'target_indexable_id', $indexable_ids )
			->group_by( 'target_indexable_id' )
			->find_array();

		// If the above query fails, do not update anything.
		if ( ! \is_array( $indexable_counts ) ) {
			return [];
		}

		// Get all ID's returned from the query and set them as keys for easy access.
		$returned_ids = \array_flip( \array_column( $indexable_counts, 'target_indexable_id' ) );

		// Loop over the original ID's and search them in the returned ID's. If they don't exist, add them with an incoming count of 0.
		foreach ( $indexable_ids as $id ) {
			// Cast the ID to string, as the arrays only contain stringified versions of the ID.
			$id = (string) $id;
			if ( isset( $returned_ids[ $id ] ) === false ) {
				$indexable_counts[] = [
					'incoming'            => '0',
					'target_indexable_id' => $id,
				];
			}
		}

		return $indexable_counts;
	}

	/**
	 * Deletes all seo links for the given ids.
	 *
	 * @param int[] $ids The seo link ids.
	 *
	 * @return bool Whether or not the delete was successful.
	 */
	public function delete_many_by_id( $ids ) {
		return $this->query()
			->where_in( 'id', $ids )
			->delete_many();
	}

	/**
	 * Insert multiple seo links.
	 *
	 * @param SEO_Links[] $links The seo links to be inserted.
	 *
	 * @return bool Whether or not the insert was successful.
	 */
	public function insert_many( $links ) {
		return $this->query()
			->insert_many( $links );
	}
}
