<?php
/**
 * Site content retrieval.
 *
 * @package OpenRouterSiteChatbotByJayanta
 */

defined( 'ABSPATH' ) || exit;

/**
 * Builds and searches a compact index of public WordPress content.
 */
class ORSC_Content_Index {
	/**
	 * Settings.
	 *
	 * @var array
	 */
	private $settings;

	/**
	 * Constructor.
	 *
	 * @param array $settings Plugin settings.
	 */
	public function __construct( $settings ) {
		$this->settings = $settings;
	}

	/**
	 * Build and store the content index.
	 *
	 * @return array
	 */
	public function rebuild() {
		$documents = $this->collect_documents();
		$index     = array(
			'built_at'   => current_time( 'mysql' ),
			'site_url'   => home_url( '/' ),
			'documents'  => $documents,
			'doc_count'  => count( $documents ),
			'version'    => ORSC_VERSION,
		);

		update_option( ORSC_INDEX_OPTION, $index, false );

		return $index;
	}

	/**
	 * Return the saved index, building it when missing.
	 *
	 * @return array
	 */
	public function get_index() {
		$index = get_option( ORSC_INDEX_OPTION, array() );

		if ( empty( $index['documents'] ) || ! is_array( $index['documents'] ) ) {
			$index = $this->rebuild();
		}

		return $index;
	}

	/**
	 * Search relevant content snippets.
	 *
	 * @param string $question Visitor question.
	 * @return array
	 */
	public function search( $question ) {
		$index     = $this->get_index();
		$documents = isset( $index['documents'] ) && is_array( $index['documents'] ) ? $index['documents'] : array();
		return $this->rank_documents( $documents, $question );
	}

	/**
	 * Search live public content when the saved index is stale.
	 *
	 * @param string $question Visitor question.
	 * @return array
	 */
	public function live_search( $question ) {
		$query     = new WP_Query(
			array(
				'post_type'              => $this->public_post_types(),
				'post_status'            => 'publish',
				's'                      => $question,
				'posts_per_page'         => max( 1, absint( $this->settings['max_sources'] ) ),
				'ignore_sticky_posts'    => true,
				'no_found_rows'          => true,
				'update_post_meta_cache' => true,
				'update_post_term_cache' => false,
			)
		);
		$documents = array();

		foreach ( $query->posts as $post ) {
			$documents[] = $this->post_document( $post );
		}

		wp_reset_postdata();

		return $this->rank_documents( array_filter( $documents ), $question );
	}

	/**
	 * Rank documents against a question.
	 *
	 * @param array  $documents Documents.
	 * @param string $question Visitor question.
	 * @return array
	 */
	private function rank_documents( $documents, $question ) {
		$terms  = $this->tokenize( $question );
		$ranked = array();

		foreach ( $documents as $document ) {
			$haystack = strtolower( $document['title'] . ' ' . $document['excerpt'] . ' ' . $document['content'] );
			$score    = 0;

			if ( strtolower( trim( $question ) ) === strtolower( $document['title'] ) ) {
				$score += 10;
			}

			foreach ( $terms as $term ) {
				$score += substr_count( $haystack, $term );
				if ( false !== strpos( strtolower( $document['title'] ), $term ) ) {
					$score += 3;
				}
			}

			if ( $score > 0 ) {
				$document['score'] = $score;
				$ranked[]          = $document;
			}
		}

		usort(
			$ranked,
			function ( $a, $b ) {
				return $b['score'] - $a['score'];
			}
		);

		$limit = max( 1, absint( $this->settings['max_sources'] ) );
		return array_slice( $ranked, 0, $limit );
	}

	/**
	 * Create prompt context from sources.
	 *
	 * @param array $sources Source documents.
	 * @return string
	 */
	public function context_text( $sources ) {
		$chunks = array();
		$budget = max( 1000, absint( $this->settings['context_characters'] ) );
		$used   = 0;

		foreach ( $sources as $index => $source ) {
			$chunk = sprintf(
				"Source %d\nTitle: %s\nURL: %s\nContent: %s",
				$index + 1,
				$source['title'],
				$source['url'],
				$source['content']
			);

			$remaining = $budget - $used;
			if ( $remaining <= 0 ) {
				break;
			}

			if ( strlen( $chunk ) > $remaining ) {
				$chunk = substr( $chunk, 0, $remaining );
			}

			$chunks[] = $chunk;
			$used    += strlen( $chunk );
		}

		return implode( "\n\n---\n\n", $chunks );
	}

	/**
	 * Collect public documents.
	 *
	 * @return array
	 */
	private function collect_documents() {
		$post_types = $this->public_post_types();
		$limit      = max( 10, absint( $this->settings['index_limit'] ) );
		$query      = new WP_Query(
			array(
				'post_type'              => $post_types,
				'post_status'            => 'publish',
				'posts_per_page'         => $limit,
				'orderby'                => 'modified',
				'order'                  => 'DESC',
				'ignore_sticky_posts'    => true,
				'no_found_rows'          => true,
				'update_post_meta_cache' => false,
				'update_post_term_cache' => false,
			)
		);
		$documents  = array();

		foreach ( $query->posts as $post ) {
			$document = $this->post_document( $post );

			if ( $document ) {
				$documents[] = $document;
			}
		}

		wp_reset_postdata();

		return $documents;
	}

	/**
	 * Convert a post to an index document.
	 *
	 * @param WP_Post $post Post object.
	 * @return array|null
	 */
	private function post_document( $post ) {
		$content = $this->clean_content( $post );
		$title   = get_the_title( $post );

		if ( '' === $content && '' === $title ) {
			return null;
		}

		return array(
			'id'       => absint( $post->ID ),
			'title'    => $title,
			'url'      => get_permalink( $post ),
			'type'     => get_post_type( $post ),
			'excerpt'  => wp_strip_all_tags( get_the_excerpt( $post ) ),
			'content'  => $content,
			'modified' => get_post_modified_time( 'c', true, $post ),
		);
	}

	/**
	 * Get allowed public post types.
	 *
	 * @return array
	 */
	private function public_post_types() {
		$selected = isset( $this->settings['post_types'] ) && is_array( $this->settings['post_types'] ) ? $this->settings['post_types'] : array( 'post', 'page' );
		$public   = get_post_types( array( 'public' => true ), 'names' );
		$allowed  = array_values( array_intersect( $selected, $public ) );

		return $allowed ? $allowed : array( 'post', 'page' );
	}

	/**
	 * Clean post content for AI context.
	 *
	 * @param WP_Post $post Post object.
	 * @return string
	 */
	private function clean_content( $post ) {
		$content = do_blocks( strip_shortcodes( $post->post_content ) );
		$content = wp_strip_all_tags( $content );
		$content = preg_replace( '/\s+/', ' ', $content );
		$content = trim( html_entity_decode( $content, ENT_QUOTES, get_bloginfo( 'charset' ) ) );
		$product = $this->product_details( $post );

		if ( $product ) {
			$content = trim( $product . ' ' . $content );
		}

		return wp_html_excerpt( $content, 2200, '' );
	}

	/**
	 * WooCommerce product details for product-aware answers.
	 *
	 * @param WP_Post $post Post object.
	 * @return string
	 */
	private function product_details( $post ) {
		if ( 'product' !== get_post_type( $post ) ) {
			return '';
		}

		$details = array( 'Product name: ' . get_the_title( $post ) );
		$sku     = get_post_meta( $post->ID, '_sku', true );
		$price   = get_post_meta( $post->ID, '_price', true );
		$regular = get_post_meta( $post->ID, '_regular_price', true );
		$sale    = get_post_meta( $post->ID, '_sale_price', true );

		if ( '' !== $sku ) {
			$details[] = 'SKU: ' . $sku;
		}

		if ( '' !== $price ) {
			$details[] = 'Current price: ' . $this->format_price( $price );
		}

		if ( '' !== $regular && $regular !== $price ) {
			$details[] = 'Regular price: ' . $this->format_price( $regular );
		}

		if ( '' !== $sale ) {
			$details[] = 'Sale price: ' . $this->format_price( $sale );
		}

		return implode( '. ', $details ) . '.';
	}

	/**
	 * Format a WooCommerce price when WooCommerce helpers are available.
	 *
	 * @param string $price Product price.
	 * @return string
	 */
	private function format_price( $price ) {
		if ( function_exists( 'wc_price' ) ) {
			return trim( wp_strip_all_tags( wc_price( $price ) ) );
		}

		return sanitize_text_field( (string) $price );
	}

	/**
	 * Tokenize a search query.
	 *
	 * @param string $text Text.
	 * @return array
	 */
	private function tokenize( $text ) {
		$text  = strtolower( wp_strip_all_tags( $text ) );
		$parts = preg_split( '/[^a-z0-9]+/', $text );
		$terms = array();

		foreach ( $parts as $part ) {
			if ( strlen( $part ) >= 3 ) {
				$terms[] = $part;
			}
		}

		return array_values( array_unique( $terms ) );
	}
}
