<?php

namespace FleekDash\Core;

use FleekDash\Services\SearchCacheService;
use FleekDash\Core\Search\SearchHelpers;
use FleekDash\Core\Search\SearchIndexBuilder;
use FleekDash\Core\Search\SearchIncrementalIndexer;
use FleekDash\Core\Search\SearchHooks;

/**
 * Main manager for search indexing and retrieval
 *
 * @package FleekDash\Core
 */
class SearchIndexManager
{
    use SearchHooks;

    private SearchCacheService $cacheService;
    private float $lastSearchTime = 0.0;

    public function __construct()
    {
        $this->cacheService = new SearchCacheService();
        $this->initHooks();
    }

    /**
     * Run initial index if needed
     */
    public function maybeRunInitialIndex()
    {
        $last_indexed = \get_option('fleekdash_search_last_indexed', 0);
        $indexing_in_progress = \get_option('fleekdash_search_indexing_in_progress', false);

        if ($last_indexed === 0 && !$indexing_in_progress) {
            $this->scheduleImmediateIndex();
        }
    }

    /**
     * Schedule immediate indexing (non-blocking)
     */
    public function scheduleImmediateIndex()
    {
        if (!\wp_next_scheduled('fleekdash_search_immediate_index')) {
            \wp_schedule_single_event(\time() + 30, 'fleekdash_search_immediate_index');
            \add_action('fleekdash_search_immediate_index', [$this, 'runBackgroundIndex']);
        }
    }

    /**
     * Run full background indexing
     */
    public function runBackgroundIndex()
    {
        if (\get_option('fleekdash_search_indexing_in_progress', false)) {
            return;
        }

        \update_option('fleekdash_search_indexing_in_progress', true);

        try {
            $start_time = microtime(true);
            $result = $this->rebuildIndex(['post', 'user', 'menu', 'setting']);
            $execution_time = round(microtime(true) - $start_time, 2);

            $stats = [
                'total_indexed' => $result['total_indexed'],
                'last_full_index' => time(),
                'execution_time' => $execution_time,
                'index_errors' => []
            ];

            \update_option('fleekdash_search_index_stats', json_encode($stats));
            \update_option('fleekdash_search_last_indexed', time());
            $this->cacheService->clearAllSearchCache();

            if (\function_exists('fleekdash_log_info')) {
                fleekdash_log_info("FleekDash Search: Full index completed. Indexed {$result['total_indexed']} items");
            }

        } catch (\Exception $e) {
            if (\function_exists('fleekdash_log_error')) {
                fleekdash_log_error('FleekDash Search Index Error: ' . $e->getMessage());
            }
        } finally {
            \update_option('fleekdash_search_indexing_in_progress', false);
        }
    }

    /**
     * Run incremental indexing
     */
    public function runIncrementalIndex()
    {
        $last_incremental = \get_option('fleekdash_search_last_incremental', time() - 900);

        try {
            $indexed_count = 0;
            $indexed_count += SearchIncrementalIndexer::indexRecentPosts($this, $last_incremental);
            $indexed_count += SearchIncrementalIndexer::indexRecentUsers($this, $last_incremental);

            if (time() - $last_incremental > 3600) {
                $indexed_count += $this->rebuildMenuIndex();
            }

            \update_option('fleekdash_search_last_incremental', time());

            if ($indexed_count > 0) {
                $this->cacheService->clearAllSearchCache();
            }

        } catch (\Exception $e) {
            if (\function_exists('fleekdash_log_error')) {
                fleekdash_log_error('FleekDash Search Incremental Index Error: ' . $e->getMessage());
            }
        }
    }

    /**
     * Search core method
     *
     * @param string       $query Search query
     * @param string[]     $types Object types to search
     * @param int          $limit Max results
     * @return array<int, array<string, mixed>>
     */
    public function search(string $query, array $types = ['post', 'user', 'menu', 'setting'], int $limit = 20): array
    {
        $start_time = microtime(true);
        global $wpdb;
        $table_name = $wpdb->prefix . 'fleekdash_search_index';

        if ($wpdb->get_var("SHOW TABLES LIKE '$table_name'") !== $table_name) {
            $this->lastSearchTime = microtime(true) - $start_time;
            return [];
        }

        $search_term = \sanitize_text_field($query);
        $expanded_terms = $this->expandWithSynonyms($search_term);
        $safe_types = array_map('sanitize_text_field', $types);
        $expanded_search_text = implode(' ', $expanded_terms);

        $like_conditions = [];
        $like_args = [];
        foreach ($expanded_terms as $term) {
            $like_conditions[] = "title LIKE %s OR content LIKE %s OR keywords LIKE %s";
            $escaped = '%' . $wpdb->esc_like($term) . '%';
            $like_args = array_merge($like_args, [$escaped, $escaped, $escaped]);
        }

        $type_boosts = $this->getTypeBoosts();
        $placeholders = implode(',', array_fill(0, count($safe_types), '%s'));

        $sql = "SELECT object_id as id, object_type as type, title, excerpt, metadata,
                (MATCH(title, content, keywords) AGAINST(%s IN NATURAL LANGUAGE MODE) * 2 +
                 MATCH(search_vector) AGAINST(%s IN NATURAL LANGUAGE MODE) +
                 (popularity_score * 0.1) +
                 CASE WHEN LOWER(title) LIKE LOWER(%s) THEN 10.0
                      WHEN LOWER(title) LIKE LOWER(CONCAT('%%', %s, '%%')) THEN 5.0
                      ELSE 0.0 END) as relevance_score
                FROM {$table_name}
                WHERE object_type IN ({$placeholders})
                AND (MATCH(title, content, keywords) AGAINST(%s IN NATURAL LANGUAGE MODE)
                     OR MATCH(search_vector) AGAINST(%s IN NATURAL LANGUAGE MODE)
                     OR (" . implode(' OR ', $like_conditions) . "))
                ORDER BY relevance_score DESC, popularity_score DESC LIMIT %d";

        $args = array_merge([$expanded_search_text, $expanded_search_text, $search_term, $search_term], $safe_types, [$expanded_search_text, $expanded_search_text], $like_args, [$limit]);
        $results = $wpdb->get_results($wpdb->prepare($sql, ...$args));

        $processed = [];
        foreach ($results as $result) {
            $meta = json_decode($result->metadata, true) ?: [];
            $display_type = ($result->type === 'post' && !empty($meta['post_type'])) ? $meta['post_type'] : $result->type;

            $boost = $type_boosts[$display_type] ?? $type_boosts[$result->type] ?? 1.0;
            if ($result->type === 'post') {
                $boost = $type_boosts['post'] ?? 6.0;
            }

            $preview_data = $this->getPreviewData((string) $result->type, (int) $result->id);

            $processed[] = [
                'id' => (int) $result->id,
                'type' => $display_type,
                'title' => $result->title,
                'excerpt' => $result->excerpt,
                'url' => $this->getObjectUrl($result->type, $result->id),
                'metadata' => $meta,
                'relevance_score' => (float)$result->relevance_score * $boost,
                'popularity_score' => $meta['popularity_score'] ?? 0,
                'preview_data' => $preview_data,
                'avatar' => $preview_data['avatar'] ?? '',
                'image' => $preview_data['image'] ?? ($preview_data['avatar'] ?? '')
            ];
        }

        usort($processed, function($a, $b) { return $b['relevance_score'] <=> $a['relevance_score']; });
        $this->lastSearchTime = microtime(true) - $start_time;
        return $processed;
    }

    // Individual item indexing helpers (delegates)
    public function indexSinglePost(int $id) { return SearchIndexBuilder::indexSinglePost($id); }
    public function removePostFromIndex(int $id) { return SearchIndexBuilder::removePostFromIndex($id); }
    public function indexSingleUser(int $id) { return SearchIndexBuilder::indexSingleUser($id); }
    public function removeUserFromIndex(int $id) { return SearchIndexBuilder::removeUserFromIndex($id); }
    public function indexSingleMenu() { return SearchIndexBuilder::indexSingleMenu(); }
    public function removeMenuFromIndex(int|string $id) { return SearchIndexBuilder::removeMenuFromIndex($id); }
    public function rebuildMenuIndex() { return SearchIndexBuilder::rebuildMenuIndex(); }

    /**
     * @param string[] $types
     * @return array{total_indexed: int}
     */
    public function rebuildIndex(array $types = ['post', 'user', 'menu', 'setting']): array
    {
        $total = 0;
        foreach ($types as $type) {
            if ($type === 'post') {
                $total += SearchIndexBuilder::rebuildPostIndex();
            }
            if ($type === 'user') {
                $total += SearchIndexBuilder::rebuildUserIndex();
            }
            if ($type === 'menu') {
                $total += SearchIndexBuilder::rebuildMenuIndex();
            }
            if ($type === 'setting') {
                $total += SearchIndexBuilder::rebuildSettingIndex();
            }
        }
        return ['total_indexed' => $total];
    }

    public function getIndexingStatus()
    {
        $stats = json_decode(\get_option('fleekdash_search_index_stats', '{}') ?? '{}', true);
        return [
            'in_progress' => \get_option('fleekdash_search_indexing_in_progress', false),
            'last_indexed' => \get_option('fleekdash_search_last_indexed', 0),
            'total_indexed' => $stats['total_indexed'] ?? 0,
            'execution_time' => $stats['execution_time'] ?? 0,
        ];
    }

    public function getLastSearchTime(): float { return $this->lastSearchTime; }
    public function getTypeBoosts(): array { return SearchHelpers::getTypeBoosts(); }
    public function getObjectUrl(string $type, int $id): string { return SearchHelpers::getObjectUrl($type, $id); }
    public function getPreviewData(string $type, int $id): array { return SearchHelpers::getPreviewData($type, $id); }
    public function expandWithSynonyms(string $searchTerm): array { return SearchHelpers::expandWithSynonyms($searchTerm); }
}
