<?php

declare(strict_types=1);

namespace Inpsyde\MultilingualPress\TranslationUi\Term\Ajax;

use Inpsyde\MultilingualPress\Framework\Database\Exception\NonexistentTable;
use Inpsyde\MultilingualPress\Framework\Http\Request;
use Inpsyde\MultilingualPress\TranslationUi\Term\RelationshipContext;
use WP_Term;
use WP_Term_Query;

use function Inpsyde\MultilingualPress\translationIds;

/**
 * @psalm-type termId = int
 * @psalm-type title = string
 */
class Search
{
    public const ACTION = 'multilingualpress_remote_term_search_arguments';
    public const FILTER_REMOTE_ARGUMENTS = 'multilingualpress.remote_term_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->findTerm($searchQuery, $context));
    }

    /**
     * Finds the term by given search query.
     *
     * @param string $searchQuery The search query.
     * @param RelationshipContext $context
     * @return array<int, string> A map of term ID to term title.
     * @psalm-return array<termId, title>
     * @throws NonexistentTable
     */
    public function findTerm(string $searchQuery, RelationshipContext $context): array
    {
        $args = [
            'taxonomy' => $context->sourceTerm()->taxonomy,
            'hide_empty' => false,
            'search' => rtrim($searchQuery),
        ];

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

        if ($context->hasRemoteTerm()) {
            $args['exclude'] = [$context->remoteTerm()->term_id];
        }

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

        $sourceSiteId = get_current_blog_id();

        switch_to_blog($context->remoteSiteId());

        $query = new WP_Term_Query();
        $terms = $query->query($args);

        $termIdsToTitlesMap = array_map(
            function (WP_Term $term) use ($sourceSiteId): array {
                $termId = $term->term_id;
                $title = $term->name;

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

                return ['id' => $termId, 'title' => $title];
            },
            $terms
        );
        restore_current_blog();

        return $termIdsToTitlesMap ?? [];
    }

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