<?php

declare(strict_types=1);

namespace Inpsyde\MultilingualPress\TranslationUi\Post\Ajax;

use Inpsyde\MultilingualPress\Framework\Database\Exception\NonexistentTable;
use Inpsyde\MultilingualPress\Framework\Http\Request;
use Inpsyde\MultilingualPress\TranslationUi\Post\RelationshipContext;
use WP_Post;

use function Inpsyde\MultilingualPress\translationIds;

/**
 * @psalm-type postId = int
 * @psalm-type title = string
 */
class Search
{
    public const ACTION = 'multilingualpress_remote_post_search';
    public const FILTER_REMOTE_ARGUMENTS = 'multilingualpress.remote_post_search_arguments';

    /**
     * @var Request
     */
    private $request;

    /**
     * @var ContextBuilder
     */
    private $contextBuilder;

    /**
     * @var string
     */
    protected $alreadyConnectedNotice;

    public function __construct(Request $request, ContextBuilder $contextBuilder, string $alreadyConnectedNotice)
    {
        $this->request = $request;
        $this->contextBuilder = $contextBuilder;
        $this->alreadyConnectedNotice = $alreadyConnectedNotice;
    }

    /**
     * Handle AJAX request.
     */
    public function handle()
    {
        if (!wp_doing_ajax()) {
            return;
        }

        if (!doing_action('wp_ajax_' . self::ACTION)) {
            wp_send_json_error('Invalid action.');
        }

        $searchQuery = (string)$this->request->bodyValue(
            'search',
            INPUT_POST,
            FILTER_SANITIZE_SPECIAL_CHARS,
            FILTER_FLAG_NO_ENCODE_QUOTES
        );

        if (!$searchQuery) {
            wp_send_json_error('Missing data.');
        }

        $context = $this->contextBuilder->build();

        wp_send_json_success($this->findPosts($searchQuery, $context));
    }

    /**
     * Finds the post by given search query.
     *
     * @param string $searchQuery The search query.
     * @param RelationshipContext $context
     * @return array<int, string> A map of post ID to post title.
     * @psalm-return array<postId, title>
     * @throws NonexistentTable
     */
    public function findPosts(string $searchQuery, RelationshipContext $context): array
    {
        $args = [
            'post_type' => $context->sourcePost()->post_type,
            's' => $searchQuery,
            'posts_per_page' => 1000,
            'orderby' => 'relevance',
            'post_status' => [
                'draft',
                'future',
                'private',
                'publish',
            ],
        ];

        if (is_numeric($searchQuery)) {
            $args['post__in'] = [(int) $searchQuery];
            // Since we're explicitly searching for a post ID, we can ignore the 's' parameter
            unset($args['s']);
        }

        if ($context->hasRemotePost()) {
            $args['post__not_in'] = [$context->remotePost()->ID];
        }

        /**
         * Filters the query arguments for the remote post search.
         *
         * @param array $args
         */
        $args = (array)apply_filters(self::FILTER_REMOTE_ARGUMENTS, $args);

        $sourceSiteId = get_current_blog_id();

        switch_to_blog($context->remoteSiteId());

        $posts = get_posts($args);

        $postIdsToTitlesMap = array_map(
            function (WP_Post $post) use ($sourceSiteId): array {
                $postId = (int)$post->ID;
                $title = $post->post_title;

                $title = $this->isConnectedWithPostOfSite($postId, $sourceSiteId)
                    ? "{$title} ($this->alreadyConnectedNotice)"
                    : $title;

                return ['id' => $postId, 'title' => $title];
            },
            $posts
        );

        restore_current_blog();

        return $postIdsToTitlesMap ?? [];
    }

    /**
     * Checks if the post with given post ID is connected to any post from given site ID.
     *
     * @param int $postId The post ID.
     * @param int $siteId The site ID.
     * @return bool true if is connected, otherwise false.
     * @throws NonexistentTable
     */
    protected function isConnectedWithPostOfSite(int $postId, int $siteId): bool
    {
        $translations = translationIds($postId);
        return in_array($postId, $translations, true) && $translations[$siteId];
    }
}
