<?php

namespace CelerSearch\Observers;

defined( 'ABSPATH' ) || exit;

use CelerSearch\Utilities\Logger;

/**
 * Observes post changes and syncs them to the search index
 */
class PostChangesObserver extends BaseObserver {

	/**
	 * Array of post IDs that have been deleted in this request
	 * Used to prevent race conditions when a post is deleted then re-saved
	 *
	 * @var array
	 */
	private array $deleted_posts = [];

	/**
	 * Start observing for changes
	 *
	 * @return void
	 */
	public function observe(): void {
		// Post changes - use wp_after_insert_post instead of save_post
		// This fires after all meta has been saved, ensuring complete data
		add_action( 'wp_after_insert_post', [ $this, 'on_post_saved' ], 10, 2 );

		// Delete handlers - before_delete_post fires while meta is still available
		add_action( 'before_delete_post', [ $this, 'on_post_deleted' ] );
		add_action( 'trashed_post', [ $this, 'on_post_trashed' ] );

		// Meta changes that should trigger re-indexing
		add_action( 'added_post_meta', [ $this, 'on_meta_change' ], 10, 4 );
		add_action( 'updated_post_meta', [ $this, 'on_meta_change' ], 10, 4 );
		add_action( 'deleted_post_meta', [ $this, 'on_meta_change' ], 10, 4 );

		// Attachment handling - these don't trigger standard post hooks
		add_action( 'add_attachment', [ $this, 'on_attachment_saved' ] );
		add_action( 'attachment_updated', [ $this, 'on_attachment_saved' ] );
		add_action( 'delete_attachment', [ $this, 'on_post_deleted' ] );
	}

	/**
	 * Handle post save event
	 *
	 * @param int           $post_id Post ID.
	 * @param \WP_Post|null $post    Post object.
	 *
	 * @return void
	 */
	public function on_post_saved( int $post_id, $post = null ): void {
		// Skip autosaves
		if ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) {
			Logger::channel( Logger::CHANNEL_OBSERVERS )->debug( 'Post sync skipped - autosave', [
				'post_id' => $post_id,
			] );

			return;
		}

		// Skip if this post was deleted in this request (race condition prevention)
		if ( in_array( $post_id, $this->deleted_posts, true ) ) {
			Logger::channel( Logger::CHANNEL_OBSERVERS )->debug( 'Post sync skipped - recently deleted', [
				'post_id' => $post_id,
			] );

			return;
		}

		// Get the post if not provided
		$post = $post ?? get_post( $post_id );

		if ( ! $post ) {
			Logger::channel( Logger::CHANNEL_OBSERVERS )->warning( 'Post sync skipped - post not found', [
				'post_id' => $post_id,
			] );

			return;
		}

		// Check if the index supports this post type before queuing
		$candidate = new \CelerSearch\DataTransfer\IndexCandidate( $post );
		if ( ! $this->index->is_supported( $candidate ) ) {
			return;
		}

		// Allow filtering whether to sync this post
		if ( ! apply_filters( 'celersearch_should_sync_post', true, $post, $this->index ) ) {
			Logger::channel( Logger::CHANNEL_OBSERVERS )->debug( 'Post sync blocked by filter', [
				'post_id'    => $post->ID,
				'post_title' => $post->post_title,
				'filter'     => 'celersearch_should_sync_post',
				'index_slug' => $this->index->get_slug(),
			] );

			return;
		}

		// Use queue if enabled, otherwise sync immediately
		if ( $this->should_use_queue() ) {
			Logger::channel( Logger::CHANNEL_OBSERVERS )->info( 'Post sync queued', [
				'post_id'     => $post->ID,
				'post_title'  => $post->post_title,
				'post_type'   => $post->post_type,
				'post_status' => $post->post_status,
				'index_slug'  => $this->index->get_slug(),
			] );

			$this->queue_sync( 'post', $post->ID );
			return;
		}

		Logger::channel( Logger::CHANNEL_OBSERVERS )->info( 'Post sync triggered', [
			'post_id'     => $post->ID,
			'post_title'  => $post->post_title,
			'post_type'   => $post->post_type,
			'post_status' => $post->post_status,
			'index_slug'  => $this->index->get_slug(),
		] );

		$this->sync_item( $post );
	}

	/**
	 * Handle attachment save event
	 *
	 * @param int $attachment_id Attachment ID.
	 *
	 * @return void
	 */
	public function on_attachment_saved( int $attachment_id ): void {
		$this->on_post_saved( $attachment_id );
	}

	/**
	 * Handle post delete event
	 *
	 * @param int $post_id Post ID.
	 *
	 * @return void
	 */
	public function on_post_deleted( int $post_id ): void {
		$post = get_post( $post_id );

		if ( ! $post ) {
			return;
		}

		// Check if the index supports this post type before queuing
		$candidate = new \CelerSearch\DataTransfer\IndexCandidate( $post );
		if ( ! $this->index->is_supported( $candidate ) ) {
			return;
		}

		// Allow filtering whether to process this deletion
		if ( ! apply_filters( 'celersearch_should_delete_post', true, $post, $this->index ) ) {
			Logger::channel( Logger::CHANNEL_OBSERVERS )->debug( 'Post delete blocked by filter', [
				'post_id'    => $post->ID,
				'post_title' => $post->post_title,
				'filter'     => 'celersearch_should_delete_post',
				'index_slug' => $this->index->get_slug(),
			] );

			return;
		}

		// Track deleted posts to prevent race conditions
		// We do this early because for queued deletes, the post won't exist when processed
		$this->deleted_posts[] = $post_id;

		// Use queue if enabled, otherwise delete immediately
		if ( $this->should_use_queue() ) {
			Logger::channel( Logger::CHANNEL_OBSERVERS )->info( 'Post delete queued', [
				'post_id'    => $post->ID,
				'post_title' => $post->post_title,
				'post_type'  => $post->post_type,
				'index_slug' => $this->index->get_slug(),
			] );

			$this->queue_delete( 'post', $post->ID );
			return;
		}

		Logger::channel( Logger::CHANNEL_OBSERVERS )->info( 'Post delete triggered', [
			'post_id'    => $post->ID,
			'post_title' => $post->post_title,
			'post_type'  => $post->post_type,
			'index_slug' => $this->index->get_slug(),
		] );

		$this->delete_item( $post );
	}

	/**
	 * Handle post trash event
	 *
	 * @param int $post_id Post ID.
	 *
	 * @return void
	 */
	public function on_post_trashed( int $post_id ): void {
		$this->on_post_deleted( $post_id );
	}

	/**
	 * Handle post meta changes
	 *
	 * @param int|array $meta_id    Meta ID(s).
	 * @param int       $post_id    Post ID.
	 * @param string    $meta_key   Meta key.
	 * @param mixed     $meta_value Meta value.
	 *
	 * @return void
	 */
	public function on_meta_change( $meta_id, int $post_id, string $meta_key, $meta_value ): void {
		// Default meta keys that trigger re-indexing
		$watched_keys = [ '_thumbnail_id' ];

		// Allow filtering which meta keys trigger re-indexing
		$watched_keys = (array) apply_filters( 'celersearch_watch_post_meta_keys', $watched_keys, $post_id );

		// Only proceed if this is a watched meta key
		if ( ! in_array( $meta_key, $watched_keys, true ) ) {
			return;
		}

		Logger::channel( Logger::CHANNEL_OBSERVERS )->debug( 'Meta change triggered resync', [
			'post_id'    => $post_id,
			'meta_key'   => $meta_key,
			'index_slug' => $this->index->get_slug(),
		] );

		// Re-sync the post
		$this->on_post_saved( $post_id );
	}
}
