<?php

namespace CelerSearch\Views;

defined( 'ABSPATH' ) || exit;

use CelerSearch\Factories\IndexFactory;
use CelerSearch\Indices\BaseIndex;
use CelerSearch\Interfaces\IRegistrable;
use WP_Error;
use WP_REST_Request;
use WP_REST_Response;
use WP_REST_Server;

/**
 * REST API endpoint for generic search views
 *
 * GET /wp-json/celersearch/v1/search
 * Returns hits, facets, facet_stats, and pagination.
 */
class RestApi implements IRegistrable {

	const NAMESPACE = 'celersearch/v1';

	/**
	 * @inheritDoc
	 */
	public function register(): void {
		add_action( 'rest_api_init', [ $this, 'register_routes' ] );
	}

	/**
	 * Register REST routes
	 *
	 * @return void
	 */
	public function register_routes(): void {
		register_rest_route( self::NAMESPACE, '/search', [
			'methods'             => WP_REST_Server::READABLE,
			'callback'            => [ $this, 'handle_search' ],
			'permission_callback' => '__return_true',
			'args'                => [
				'q'        => [
					'required'          => false,
					'type'              => 'string',
					'default'           => '',
					'sanitize_callback' => 'sanitize_text_field',
				],
				'index_id' => [
					'required'          => true,
					'type'              => 'integer',
					'sanitize_callback' => 'absint',
				],
				'filters'  => [
					'required'          => false,
					'type'              => 'string',
					'default'           => '{}',
					'sanitize_callback' => 'sanitize_text_field',
				],
				'sort'     => [
					'required'          => false,
					'type'              => 'string',
					'default'           => '',
					'sanitize_callback' => 'sanitize_text_field',
				],
				'page'     => [
					'required'          => false,
					'type'              => 'integer',
					'default'           => 1,
					'sanitize_callback' => 'absint',
				],
				'per_page' => [
					'required'          => false,
					'type'              => 'integer',
					'default'           => 12,
					'sanitize_callback' => 'absint',
				],
			],
		] );
	}

	/**
	 * Handle search request
	 *
	 * @param WP_REST_Request $request
	 *
	 * @return WP_REST_Response|WP_Error
	 */
	public function handle_search( WP_REST_Request $request ) {
		$query    = $request->get_param( 'q' );
		$index_id = $request->get_param( 'index_id' );
		$filters  = json_decode( $request->get_param( 'filters' ), true ) ?: [];
		$sort     = $request->get_param( 'sort' );
		$page     = max( 1, $request->get_param( 'page' ) );
		$per_page = max( 1, min( 100, $request->get_param( 'per_page' ) ) );

		if ( empty( $index_id ) ) {
			return new WP_Error(
				'missing_index_id',
				__( 'The index_id parameter is required.', 'celersearch' ),
				[ 'status' => 400 ]
			);
		}

		try {
			$index = IndexFactory::create( $index_id );

			// Build filters
			$search_filters = $this->build_filters( $filters );

			// Always filter to published content
			$search_filters[] = [
				'field'    => 'status',
				'operator' => '=',
				'value'    => 'publish',
			];

			// Build facet attributes from index settings
			$facet_attributes = $this->get_facet_attributes( $index );

			/**
			 * Filter facet attributes for search view
			 *
			 * @param array     $facet_attributes The facet attributes.
			 * @param BaseIndex $index            The index.
			 */
			$facet_attributes = apply_filters( 'celersearch_view_facet_attributes', $facet_attributes, $index );

			$params = [
				'limit'  => $per_page,
				'offset' => ( $page - 1 ) * $per_page,
				'facets' => $facet_attributes,
				'highlighting' => [
					'enabled'  => true,
					'fields'   => [ 'title', 'content' ],
					'pre_tag'  => '<em class="celersearch-highlight">',
					'post_tag' => '</em>',
				],
			];

			if ( ! empty( $search_filters ) ) {
				$params['filters'] = $search_filters;
			}

			if ( ! empty( $sort ) ) {
				$sort_rules = $index->resolve_sort( $sort );
				if ( ! empty( $sort_rules ) ) {
					$params['sort'] = $sort_rules;
				}
			}

			/**
			 * Filter search params before execution
			 *
			 * @param array     $params The search parameters.
			 * @param string    $query  The search query.
			 * @param BaseIndex $index  The index.
			 */
			$params = apply_filters( 'celersearch_view_search_params', $params, $query, $index );

			$response = $index->search( $query, $params );

			if ( $response->is_error_response() ) {
				return new WP_Error(
					'search_error',
					__( 'Search failed.', 'celersearch' ),
					[ 'status' => 500 ]
				);
			}

			$hits        = $this->format_hits( $response->get_hits(), $index );
			$total       = $response->get_total_hits();
			$total_pages = $per_page > 0 ? (int) ceil( $total / $per_page ) : 1;

			return new WP_REST_Response( [
				'hits'        => $hits,
				'facets'      => $response->get_facet_distribution(),
				'facet_stats' => $response->get_facet_stats(),
				'total'       => $total,
				'page'        => $page,
				'total_pages' => $total_pages,
			] );

		} catch ( \Exception $e ) {
			return new WP_Error(
				'search_exception',
				$e->getMessage(),
				[ 'status' => 500 ]
			);
		}
	}

	/**
	 * Build normalized filter conditions from the filters param
	 *
	 * @param array $filters
	 *
	 * @return array
	 */
	private function build_filters( array $filters ): array {
		$conditions = [];

		foreach ( $filters as $key => $value ) {
			if ( empty( $value ) && $value !== false && $value !== 0 ) {
				continue;
			}

			// Range filter (e.g. price: {min: 0, max: 500})
			if ( is_array( $value ) && ( isset( $value['min'] ) || isset( $value['max'] ) ) ) {
				if ( isset( $value['min'] ) && $value['min'] > 0 ) {
					$conditions[] = [
						'field'    => $key,
						'operator' => '>=',
						'value'    => (float) $value['min'],
					];
				}
				if ( isset( $value['max'] ) && $value['max'] > 0 ) {
					$conditions[] = [
						'field'    => $key,
						'operator' => '<=',
						'value'    => (float) $value['max'],
					];
				}
				continue;
			}

			// Boolean filter
			if ( is_bool( $value ) ) {
				$conditions[] = [
					'field'    => $key,
					'operator' => '=',
					'value'    => $value,
				];
				continue;
			}

			// Array filter (IN)
			if ( is_array( $value ) ) {
				$conditions[] = [
					'field'    => $key,
					'operator' => count( $value ) === 1 ? '=' : 'IN',
					'value'    => count( $value ) === 1 ? $value[0] : $value,
				];
				continue;
			}

			// Simple equality
			$conditions[] = [
				'field'    => $key,
				'operator' => '=',
				'value'    => $value,
			];
		}

		return $conditions;
	}

	/**
	 * Get facet attributes from index settings
	 *
	 * @param BaseIndex $index
	 *
	 * @return array
	 */
	private function get_facet_attributes( BaseIndex $index ): array {
		$settings   = $index->get_settings();
		$filterable = $settings->get_filterable_attributes();
		$facets     = [];

		foreach ( $filterable as $attr ) {
			// Include taxonomy attributes
			if ( strpos( $attr, 'taxonomies.' ) === 0 || $attr === 'taxonomies' ) {
				$facets[] = $attr;
			}
		}

		return array_unique( $facets );
	}

	/**
	 * Format search hits for the response
	 *
	 * @param array     $hits  Raw hits from search index.
	 * @param BaseIndex $index The index.
	 *
	 * @return array
	 */
	private function format_hits( array $hits, BaseIndex $index ): array {
		$formatted = [];

		foreach ( $hits as $hit ) {
			$post_id = $hit['post_id'] ?? $hit['id'] ?? null;
			if ( ! $post_id ) {
				continue;
			}

			$post = get_post( (int) $post_id );
			if ( ! $post ) {
				continue;
			}

			$formatted_hit = $hit['_formatted'] ?? $hit;

			$thumbnail_id  = get_post_thumbnail_id( $post );
			$thumbnail_url = $thumbnail_id ? wp_get_attachment_image_url( $thumbnail_id, 'medium' ) : '';

			$post_type_obj = get_post_type_object( $post->post_type );

			$item = [
				'id'              => $post->ID,
				'title'           => $formatted_hit['title'] ?? $formatted_hit['post_title'] ?? get_the_title( $post ),
				'excerpt'         => $formatted_hit['content'] ?? wp_trim_words( $post->post_content, 20, '...' ),
				'url'             => get_permalink( $post ),
				'thumbnail'       => $thumbnail_url,
				'post_type'       => $post->post_type,
				'post_type_label' => $post_type_obj ? $post_type_obj->labels->singular_name : $post->post_type,
				'date'            => get_the_date( '', $post ),
				'author'          => get_the_author_meta( 'display_name', $post->post_author ),
			];

			/**
			 * Filter individual search hit data before returning to frontend.
			 *
			 * Add custom fields that become available as {{field_name}} in result templates.
			 *
			 * @param array     $item  The formatted hit data.
			 * @param \WP_Post  $post  The WordPress post object.
			 * @param array     $hit   Raw hit data from the search engine (includes all indexed fields).
			 * @param BaseIndex $index The search index instance.
			 */
			$item = apply_filters( 'celersearch_view_search_hit', $item, $post, $hit, $index );

			$formatted[] = $item;
		}

		return $formatted;
	}
}
