<?php
namespace DevOwl\SearchEnginePostType;

use WP_Post;

/**
 * Listen to post changes and incrementally update it in our search engine.
 */
class AutoUpdates {
    use UtilsProvider;

    /**
     * The post type instance.
     *
     * @var SearchEnginePostType
     */
    private $searchEnginePostType;

    /**
     * Pending post IDs which should be reindex at `shutdown` action.
     *
     * @var int[]
     */
    private $pendingPosts = [];

    /**
     * Pending post IDs which should be deleted at `shutdown` action.
     *
     * @var int[]
     */
    private $pendingDeletionPosts = [];

    /**
     * C'tor.
     *
     * @param SearchEnginePostType $searchEnginePostType
     * @codeCoverageIgnore
     */
    public function __construct($searchEnginePostType) {
        $this->searchEnginePostType = $searchEnginePostType;

        $this->hooks();
    }

    /**
     * Create hooks to listen to changes.
     */
    public function hooks() {
        add_action('save_post_' . $this->getSearchEnginePostType()->getArg('post_type'), [$this, 'save_post'], 10, 3);
        add_action('added_post_meta', [$this, 'update_or_added_post_meta'], 10, 2);
        add_action('update_post_meta', [$this, 'update_or_added_post_meta'], 10, 2);
        add_action('deleted_post', [$this, 'deleted_post'], 10, 2);
        add_action('shutdown', [$this, 'persistPending'], 15);
    }

    /**
     * A post got saved, update the post in the search engine.
     *
     * @param int $id
     * @param WP_Post $post
     * @param boolean $update
     */
    public function save_post($id, $post) {
        if (wp_is_post_revision($post) || wp_is_post_autosave($post)) {
            return;
        }

        if (get_post_status($post) !== 'publish') {
            // E.g. trashing a post
            $this->pendingDeletionPosts[] = $id;
        } else {
            $this->pendingPosts[] = $id;
        }
    }

    /**
     * A post meta got updated.
     *
     * @param int $meta_id
     * @param string $object_id
     */
    public function update_or_added_post_meta($meta_id, $object_id) {
        $searchEnginePostType = $this->getSearchEnginePostType();

        if (get_post_type($object_id) === $searchEnginePostType->getArg('post_type')) {
            $this->pendingPosts[] = $object_id;
        }
    }

    /**
     * A post got deleted.
     *
     * @param int $post_id
     * @param WP_Post $post
     */
    public function deleted_post($post_id, $post) {
        if ($post->post_type === $this->getSearchEnginePostType()->getArg('post_type')) {
            $this->pendingDeletionPosts[] = $post_id;
        }
    }

    /**
     * Update the pending posts to the search engine.
     *
     * @param WP_Post $post
     */
    public function persistPending() {
        $searchEnginePostType = $this->getSearchEnginePostType();
        $indexRecords = new IndexRecords($searchEnginePostType);

        $pending = array_unique($this->pendingPosts);
        if (count($pending) > 0) {
            // Check if distinct / splitting is active and always delete all chunks before persisting again
            if ($searchEnginePostType->getProvider()->getDistinctKey()) {
                $this->deletePostsFromIndex($pending);
            }

            $indexRecords->start(false, $pending);
        }

        $pendingDeletion = array_unique($this->pendingDeletionPosts);
        if (count($pendingDeletion) > 0) {
            $this->deletePostsFromIndex($pendingDeletion);
        }
    }

    /**
     * Delete a set of post ids from the index.
     *
     * @param int[] $ids
     */
    protected function deletePostsFromIndex($ids) {
        // We need to iterate each index (that should not be a problem as each post (also duplicates) gets a new ID!)
        $searchEnginePostType = $this->getSearchEnginePostType();
        $compLanguage = $searchEnginePostType->getCompLanguage();
        if ($compLanguage !== null && $compLanguage->isActive()) {
            $compLanguage->iterateAllLanguagesContext(function () use ($ids, $searchEnginePostType) {
                $searchEnginePostType->getProvider()->removeFromIndex($ids);
            });
        } else {
            $searchEnginePostType->getProvider()->removeFromIndex($ids);
        }
    }

    /**
     * Getter.
     *
     * @codeCoverageIgnore
     */
    public function getSearchEnginePostType() {
        return $this->searchEnginePostType;
    }
}
