<?php

namespace WPDRMS\ASP\Index;

use Closure;
use WPDRMS\ASP\Index\ORM\IndexTable;
use WPDRMS\ASP\Index\ORM\IndexTableOptions;
use WPDRMS\PluginCore\Traits\SingletonTrait;

if ( !defined('ABSPATH') ) {
	die('-1');
}

class IndexTableWorker {
	use SingletonTrait;

	/** @var array<string, Closure> */
	private array $queue = array();

	/**
	 * Queues a document index update triggered by a post save hook.
	 * Returns false if the post should not be indexed (revision, already queued, filtered out).
	 *
	 * @param int|null $post_id
	 * @param mixed    $_post
	 * @param bool     $update
	 */
	public function queueUpdate( ?int $post_id = null, $_post = null, bool $update = false ): bool {
		if ( !isset($post_id) || wp_is_post_revision($post_id) ) {
			return false;
		}

		$stop = apply_filters('asp_index_on_save_stop', false, $post_id, $_post, $update);
		if ( $stop ) {
			return false;
		}

		$key = 'post_' . $post_id;
		if ( isset($this->queue[ $key ]) ) {
			return false;
		}

		$this->queue[ $key ] = function () use ( $post_id, $update ): void {
			$opts = IndexTableOptions::instance();

			if ( !$opts->index_on_save->value && !$update ) {
				return;
			}

			$args             = $opts->toManagerArgs();
			$processor        = new IndexProcessor($args);
			$post_status      = get_post_status($post_id);
			$allowed_statuses = array_filter(
				explode(',', $args['post_statuses']),
				fn( $v ) => trim($v) !== ''
			);

			if ( count($allowed_statuses) === 0 ) {
				return;
			}

			foreach ( $allowed_statuses as &$v ) {
				$v = trim($v);
			}

			$allowed_statuses = apply_filters('asp/index/hooks/post_update/allowed_statuses', $allowed_statuses, $post_id);

			if ( $post_status === 'trash' || !in_array($post_status, $allowed_statuses, true) ) {
				$this->deleteDocument($post_id);
				return;
			}

			$post_type     = get_post_type($post_id);
			$allowed_types = $args['post_types'];

			if ( class_exists('WooCommerce') &&
				in_array($post_type, array( 'product', 'product_variation' ), true)
			) {
				if ( $post_type === 'product' ) {
					if ( in_array('product_variation', $allowed_types, true) ) {
						$variations = get_posts(
							array(
								'post_type'   => 'product_variation',
								'post_status' => $allowed_statuses,
								'numberposts' => -1,
								'fields'      => 'ids',
								'post_parent' => $post_id,
							)
						);
						foreach ( $variations as $variation ) {
							if ( is_numeric($variation) ) {
								$processor->indexDocument($variation, true, true);
							}
						}
					}
					if ( in_array('product', $allowed_types, true) ) {
						$processor->indexDocument($post_id, true, true);
					}
				} elseif ( in_array('product_variation', $allowed_types, true) ) {
					$parent = wp_get_post_parent_id($post_id);
					if ( $parent !== false ) {
						$parent_status = get_post_status($parent);
						if ( in_array($parent_status, $allowed_statuses, true) ) {
							$processor->indexDocument($post_id, true, true);
						}
					}
				}
			} else {
				$processor->indexDocument($post_id, true, true);
			}
		};

		return true;
	}

	/**
	 * Queues a document index update triggered by a post meta change hook.
	 *
	 * @param int $mid       The meta ID after successful update.
	 * @param int $object_id ID of the object metadata is for.
	 */
	public function queueUpdateFromMeta( int $mid, int $object_id ): void {
		if ( IndexTableOptions::instance()->index_on_update_post_meta->value ) {
			$this->queueUpdate($object_id, null, true);
		}
	}

	/** Executes all queued index operations. */
	public function flushQueue(): void {
		foreach ( $this->queue as $callable ) {
			$callable();
		}
	}

	/**
	 * Removes a post (and its WooCommerce variations) from the index.
	 *
	 * @param int $post_id
	 */
	public function deleteDocument( int $post_id ): void {
		$post_type = get_post_type( $post_id );

		if ( class_exists( 'WooCommerce' ) && $post_type === 'product' ) {
			$variations   = get_posts( array(
				'post_type'   => 'product_variation',
				'post_status' => 'any',
				'numberposts' => -1,
				'fields'      => 'ids',
				'post_parent' => $post_id,
			) );
			$variations[] = $post_id;
			IndexTable::removeDocument( $variations );
		} else {
			IndexTable::removeDocument( $post_id );
		}
	}

	/** Runs a single cron index batch and records the result. */
	public function runCronBatch(): void {
		$args = IndexTableOptions::instance()->toManagerArgs();
		$res  = IndexService::instance()->index( $args );
		IndexService::instance()->recordDailySnapshot();

		update_option(
			'asp_it_cron',
			array(
				'last_run' => time(),
				'result'   => $res,
			)
		);
	}

	/** Schedules or clears the WP-Cron index event based on current options. */
	public function scheduleCron(): void {
		$opts     = IndexTableOptions::instance();
		$enabled  = $opts->cron_enable->value;
		$period   = $opts->cron_period->value;
		$do_media = in_array( 'attachment', $opts->post_types->value, true );

		if ( ! $enabled && ! $do_media ) {
			wp_clear_scheduled_hook( 'asp_cron_it_extend' );
			return;
		}

		$desired = $do_media
			? ( ( $enabled && in_array( $period, array( 'asp_cr_two_minutes', 'asp_cr_three_minutes' ), true ) ) ? $period : 'asp_cr_five_minutes' )
			: $period;

		if ( wp_get_schedule( 'asp_cron_it_extend' ) === $desired ) {
			return;
		}

		wp_clear_scheduled_hook( 'asp_cron_it_extend' );
		wp_schedule_event( time(), $desired, 'asp_cron_it_extend' );
	}
}
