<?php

namespace CelerSearch\Services;

use Exception;
use CelerSearch\Abstracts\IndexResponse;
use CelerSearch\DataTransfer\IndexResponses\ErrorResponse;
use CelerSearch\DataTransfer\IndexResponses\SearchResponse;
use CelerSearch\DataTransfer\ServiceConfig;
use CelerSearch\Indices\BaseIndex;
use CelerSearch\Interfaces\IIndexableObject;

abstract class BaseService {

	/**
	 * The configuration object
	 * @var ServiceConfig
	 */
	protected ServiceConfig $details;

	/**
	 * Constructor
	 *
	 * @param ServiceConfig $details
	 *
	 * @throws Exception
	 */
	public function __construct( ServiceConfig $details ) {
		$this->details = $details;
		$this->init();
	}

	/**
	 * A good place to create API connection, etc.
	 * @return void
	 * @throws Exception
	 */
	abstract public function init(): void;

	/**
	 * Perform a search on the index
	 *
	 * @param BaseIndex $index
	 * @param string $query
	 * @param array $params
	 *
	 * @return IndexResponse
	 */
	abstract public function search( BaseIndex $index, string $query, array $params = [] ): IndexResponse;

	/**
	 * Format the search parameters
	 * @param array $params
	 *
	 * @return array
	 */
	abstract protected function format_search_params(array $params): array;

	/**
	 * Syncs item to the index
	 *
	 * @param BaseIndex $index
	 * @param IIndexableObject $item
	 *
	 * @return IndexResponse
	 */
	abstract public function sync_item( BaseIndex $index, IIndexableObject $item ): IndexResponse;

	/**
	 * Deletes item from the index
	 *
	 * @param BaseIndex $index
	 * @param IIndexableObject $item
	 *
	 * @return IndexResponse
	 */
	abstract public function delete_item( BaseIndex $index, IIndexableObject $item ): IndexResponse;

	/**
	 * Counts index indexed data
	 *
	 * @param BaseIndex $index
	 *
	 * @return IndexResponse
	 */
	abstract public function count_items( BaseIndex $index ): IndexResponse;

	/**
	 * Check whether the index exists
	 *
	 * @param BaseIndex $index
	 *
	 * @return IndexResponse
	 */
	abstract public function check_index( BaseIndex $index ): IndexResponse;

	/**
	 * Create the index if it doesn't exist
	 *
	 * @param BaseIndex $index
	 *
	 * @return IndexResponse
	 */
	abstract public function touch_index( BaseIndex $index ): IndexResponse;

	/**
	 * Clear the index
	 *
	 * @param BaseIndex $index
	 *
	 * @return IndexResponse
	 */
	abstract public function clear_index( BaseIndex $index ): IndexResponse;

	/**
	 * Delete the index
	 *
	 * @param BaseIndex $index
	 *
	 * @return IndexResponse
	 */
	abstract public function delete_index( BaseIndex $index ): IndexResponse;

	/**
	 * Push index settings to remote
	 *
	 * @param BaseIndex $index
	 *
	 * @return IndexResponse
	 */
	abstract public function push_settings( BaseIndex $index ): IndexResponse;

	/**
	 * Build the settings
	 *
	 * @param BaseIndex $index
	 *
	 * @return IndexResponse
	 */
	abstract public function build_settings( BaseIndex $index ): array;

	/**
	 * Returns the service settings
	 *
	 * @param BaseIndex $index
	 *
	 * @return mixed
	 */
	public function get_settings( BaseIndex $index ): array {
		$settings = apply_filters( 'celersearch_service_settings', $this->build_settings( $index ), $this );
		return apply_filters( 'celersearch_service_' . $this->details->getProvider() . '_settings', $settings, $this->details, $this );
	}

	/**
	 * List documents currently stored in the index.
	 *
	 * Callers pass `$fields` to restrict the returned payload for list-view
	 * performance (e.g. ['id', 'post_title']); an empty array returns full
	 * documents. Return shape is a DocumentListResponse carrying $documents
	 * (array), $total, $offset, $limit.
	 *
	 * @param BaseIndex $index
	 * @param int       $offset
	 * @param int       $limit
	 * @param array     $fields
	 *
	 * @return IndexResponse
	 */
	abstract public function list_documents( BaseIndex $index, int $offset, int $limit, array $fields = [] ): IndexResponse;

	/**
	 * Fetch a single document from the index by its primary-key id.
	 *
	 * Returns a ValueResponse whose `value` is the raw document array, or
	 * an ErrorResponse when the id is unknown / the engine errors.
	 *
	 * @param BaseIndex $index
	 * @param string    $id
	 *
	 * @return IndexResponse
	 */
	abstract public function get_document( BaseIndex $index, string $id ): IndexResponse;

	/**
	 * Fetch the engine-side configuration/settings for an index.
	 *
	 * Debugging surface — returns whatever the remote engine reports as the
	 * current, effective settings for the index (searchable/filterable/
	 * sortable attributes, synonyms, ranking rules, typo tolerance, etc.).
	 * Providers return the raw shape their API speaks — callers treat it as
	 * an opaque associative array suitable for JSON display. Returns a
	 * ValueResponse on success or an ErrorResponse when the engine errors.
	 *
	 * @param BaseIndex $index
	 *
	 * @return IndexResponse
	 */
	abstract public function get_index_config( BaseIndex $index ): IndexResponse;

	/**
	 * Returns a map of setting keys to their impact level for this engine.
	 *
	 * Values:
	 *   'reindex' — engine handles it automatically; the plugin does NOT
	 *               need to re-push documents but results may take a few
	 *               seconds/minutes to reflect the change on large indices.
	 *   'rebuild' — the plugin MUST re-push documents for the change to
	 *               affect existing records (use the Rebuild row action).
	 *
	 * Keys not present imply `instant`. Only engine-level impacts belong
	 * here — plugin-level rebuild triggers (e.g. content splitting) are
	 * merged on top by the Ajax layer since they apply regardless of which
	 * engine the index uses.
	 *
	 * @return array<string, string>
	 */
	public function get_setting_impacts(): array {
		return [];
	}

	/**
	 * Lightweight health check for the service
	 *
	 * Checks if the service is reachable and healthy without creating
	 * or modifying any indices or documents.
	 *
	 * @return bool True if the service is healthy, false otherwise
	 */
	abstract public function health_check(): bool;

	/**
	 * Wait for an async task to complete
	 *
	 * Providers with asynchronous operations (like MeiliSearch) should implement
	 * this method to block until the task completes. Providers with synchronous
	 * operations can simply return true.
	 *
	 * @param IndexResponse $response Response containing task info
	 * @param int           $timeout_ms Maximum wait time in milliseconds
	 *
	 * @return bool True if task completed successfully, false otherwise
	 */
	abstract public function wait_for_completion( IndexResponse $response, int $timeout_ms = 5000 ): bool;

}