<?php
if ( ! defined( 'ABSPATH' ) ) {
	exit;
}

class Arcai_Indexer {

	const STATE_OPTION = 'arcai_index_state';
	const BATCH_SIZE   = 5;
	const CRON_HOOK    = 'arcai_process_batch';
	const POST_HOOK    = 'arcai_index_post';
	const LOCK_KEY     = 'arcai_index_lock';

	private $store;
	private $provider;

	public function __construct() {
		$this->store    = new Arcai_Vector_Store();
		$this->provider = new Arcai_Provider_OpenAI();
	}

	public function register_hooks() {
		add_action( self::CRON_HOOK, array( $this, 'process_batch' ) );
		add_action( self::POST_HOOK, array( $this, 'index_single_post' ) );
		add_action( 'save_post', array( $this, 'on_save_post' ), 20, 2 );
		add_action( 'before_delete_post', array( $this, 'on_delete_post' ) );
		add_action( 'wp_trash_post', array( $this, 'on_delete_post' ) );
	}

	/**
	 * IDs of indexable posts: published, public, selected types, newest first,
	 * capped at the free-version limit.
	 */
	public function indexable_post_ids() {
		$cap = arcai_is_pro() ? -1 : (int) apply_filters( 'arcai_post_cap', ARCAI_FREE_POST_CAP );
		$ids = get_posts(
			array(
				'post_type'      => Arcai_Settings::get( 'post_types' ),
				'post_status'    => 'publish',
				'has_password'   => false,
				'posts_per_page' => $cap,
				'orderby'        => 'date',
				'order'          => 'DESC',
				'fields'         => 'ids',
				'no_found_rows'  => true,
			)
		);
		return array_map( 'intval', $ids );
	}

	/**
	 * Token + cost estimate for a full index run. No API calls.
	 */
	public function estimate() {
		$ids    = $this->indexable_post_ids();
		$tokens = 0;

		foreach ( $ids as $id ) {
			$post = get_post( $id );
			if ( $post ) {
				$tokens += Arcai_Chunker::estimate_tokens( Arcai_Chunker::plain_text( $post ) );
			}
		}

		return array(
			'posts'  => count( $ids ),
			'tokens' => $tokens,
			'cost'   => $this->provider->estimate_embedding_cost( $tokens ),
			'capped' => ! arcai_is_pro() && count( $ids ) >= (int) apply_filters( 'arcai_post_cap', ARCAI_FREE_POST_CAP ),
		);
	}

	public function start() {
		$state = $this->get_state();
		if ( 'running' === $state['status'] ) {
			return new WP_Error( 'arcai_already_running', __( 'Indexing is already running.', 'archivist-ai' ) );
		}

		$ids = $this->indexable_post_ids();
		if ( empty( $ids ) ) {
			return new WP_Error( 'arcai_nothing_to_index', __( 'No published posts found for the selected post types.', 'archivist-ai' ) );
		}

		$this->set_state(
			array(
				'status'  => 'running',
				'queue'   => $ids,
				'total'   => count( $ids ),
				'done'    => 0,
				'errors'  => 0,
				'message' => '',
			)
		);

		$this->schedule_next_batch();
		return true;
	}

	public function cancel() {
		$state           = $this->get_state();
		$state['status'] = 'cancelled';
		$state['queue']  = array();
		$this->set_state( $state );
		if ( function_exists( 'as_unschedule_all_actions' ) ) {
			as_unschedule_all_actions( self::CRON_HOOK );
		}
		wp_clear_scheduled_hook( self::CRON_HOOK );
	}

	public function get_state() {
		$state = get_option(
			self::STATE_OPTION,
			array(
				'status'  => 'idle',
				'queue'   => array(),
				'total'   => 0,
				'done'    => 0,
				'errors'  => 0,
				'message' => '',
			)
		);
		$state['indexed_posts'] = $this->store->indexed_post_count();
		return $state;
	}

	private function set_state( array $state ) {
		unset( $state['indexed_posts'] );
		update_option( self::STATE_OPTION, $state, false );
	}

	/**
	 * Action Scheduler is used when present (bundled by WooCommerce and many
	 * other plugins) — it survives hosts where WP-Cron is unreliable. Plain
	 * WP-Cron single events are the fallback.
	 */
	private function schedule_next_batch() {
		if ( function_exists( 'as_enqueue_async_action' ) ) {
			if ( false === as_next_scheduled_action( self::CRON_HOOK ) ) {
				as_enqueue_async_action( self::CRON_HOOK, array(), 'archivist-ai' );
			}
			return;
		}
		if ( ! wp_next_scheduled( self::CRON_HOOK ) ) {
			wp_schedule_single_event( time() + 1, self::CRON_HOOK );
		}
	}

	private function schedule_post_index( $post_id ) {
		if ( function_exists( 'as_schedule_single_action' ) ) {
			if ( false === as_next_scheduled_action( self::POST_HOOK, array( $post_id ) ) ) {
				as_schedule_single_action( time() + 10, self::POST_HOOK, array( $post_id ), 'archivist-ai' );
			}
			return;
		}
		if ( ! wp_next_scheduled( self::POST_HOOK, array( $post_id ) ) ) {
			wp_schedule_single_event( time() + 10, self::POST_HOOK, array( $post_id ) );
		}
	}

	/**
	 * Process one batch from the queue. Re-entrant safe via a short lock;
	 * also called inline from the admin status poll so progress doesn't
	 * stall on hosts with unreliable WP-Cron.
	 */
	public function process_batch() {
		if ( get_transient( self::LOCK_KEY ) ) {
			return;
		}
		set_transient( self::LOCK_KEY, 1, 2 * MINUTE_IN_SECONDS );

		$state = $this->get_state();
		if ( 'running' !== $state['status'] || empty( $state['queue'] ) ) {
			delete_transient( self::LOCK_KEY );
			return;
		}

		$batch          = array_splice( $state['queue'], 0, self::BATCH_SIZE );
		$failed_message = '';

		foreach ( $batch as $post_id ) {
			$result = $this->index_post_now( $post_id );
			if ( is_wp_error( $result ) ) {
				$state['errors']++;
				$failed_message = $result->get_error_message();
				if ( 'arcai_api_error' === $result->get_error_code() || 'arcai_no_key' === $result->get_error_code() ) {
					$state['status']  = 'error';
					$state['message'] = $failed_message;
					$this->set_state( $state );
					delete_transient( self::LOCK_KEY );
					return;
				}
			}
			$state['done']++;
		}

		if ( empty( $state['queue'] ) ) {
			$state['status'] = 'done';
		}
		$state['message'] = $failed_message;
		$this->set_state( $state );
		delete_transient( self::LOCK_KEY );

		if ( 'running' === $state['status'] ) {
			$this->schedule_next_batch();
		}
	}

	/**
	 * Chunk, embed, and store a single post. Returns true or WP_Error.
	 */
	public function index_post_now( $post_id ) {
		$post = get_post( $post_id );
		if ( ! $post || 'publish' !== $post->post_status || '' !== $post->post_password ) {
			$this->store->delete_post_chunks( $post_id );
			return true;
		}

		$chunks = Arcai_Chunker::chunk_post( $post );
		if ( empty( $chunks ) ) {
			$this->store->delete_post_chunks( $post_id );
			return true;
		}

		$texts  = wp_list_pluck( $chunks, 'content' );
		$result = $this->provider->embed( $texts );
		if ( is_wp_error( $result ) ) {
			return $result;
		}

		Arcai_Usage::log( 'index', $result['tokens'], 0, $result['cost'] );
		$this->store->replace_post_chunks( $post_id, $post->post_date, $chunks, $result['embeddings'] );

		return true;
	}

	public function index_single_post( $post_id ) {
		$this->index_post_now( $post_id );
	}

	public function on_save_post( $post_id, $post ) {
		if ( wp_is_post_revision( $post_id ) || wp_is_post_autosave( $post_id ) ) {
			return;
		}
		if ( ! in_array( $post->post_type, (array) Arcai_Settings::get( 'post_types' ), true ) ) {
			return;
		}
		if ( 'publish' === $post->post_status ) {
			$this->schedule_post_index( $post_id );
		} else {
			$this->store->delete_post_chunks( $post_id );
		}
	}

	public function on_delete_post( $post_id ) {
		$this->store->delete_post_chunks( $post_id );
	}
}
