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

/**
 * Service for handling mathematical ranking, vector similarity, and search heuristics.
 */
class EEP_Chatbot_Ranking_Service {

    /**
     * @var EEP_Chatbot_Knowledge_Service
     */
    private $knowledge_service;

    /**
     * @var string
     */
    private $tax_type;

    /**
     * @var string
     */
    private $tax_tag;

    /**
     * Constructor.
     *
     * @param EEP_Chatbot_Knowledge_Service $knowledge_service Required to fetch the AI catalog.
     * @param string $tax_type Taxonomy slug for event types.
     * @param string $tax_tag Taxonomy slug for event tags.
     */
    public function __construct($knowledge_service, $tax_type = 'event_type', $tax_tag = 'event_tag') {
        $this->knowledge_service = $knowledge_service;
        $this->tax_type = sanitize_key($tax_type);
        $this->tax_tag = sanitize_key($tax_tag);
    }

    /**
     * Late-binds the knowledge service dependency for circular DI resolution.
     *
     * @param mixed $knowledge_service
     * @return void
     */
    public function set_knowledge_service($knowledge_service) {
        $this->knowledge_service = $knowledge_service;
    }

    /**
     * Calculates the cosine similarity between two vectors.
     */
    public function chatbot_cosine_similarity(array $a, array $b) {
        $len = min(count($a), count($b));
        if ($len <= 0) {
            return 0.0;
        }
        $dot = 0.0;
        $norm_a = 0.0;
        $norm_b = 0.0;
        for ($i = 0; $i < $len; $i++) {
            $av = (float) $a[$i];
            $bv = (float) $b[$i];
            $dot += $av * $bv;
            $norm_a += $av * $av;
            $norm_b += $bv * $bv;
        }
        if ($norm_a <= 0 || $norm_b <= 0) {
            return 0.0;
        }
        return $dot / (sqrt($norm_a) * sqrt($norm_b));
    }

    /**
     * Extracts meaningful keywords from a user query by removing stopwords.
     */
    public function chatbot_extract_query_keywords($query) {
        $query = strtolower((string) $query);
        if ($query === '') {
            return [];
        }
        preg_match_all('/[\p{L}\p{N}\-]{2,}/u', $query, $matches);
        $tokens = isset($matches[0]) && is_array($matches[0]) ? (array) $matches[0] : [];
        
        $stopwords = [
            'the', 'and', 'for', 'with', 'from', 'about', 'show', 'more', 'list', 'all', 'any', 'event', 'events',
            'near', 'this', 'that', 'these', 'those', 'what', 'when', 'where', 'which', 'how', 'can', 'you', 'please',
            'next', 'week', 'month', 'today', 'tomorrow', 'online', 'free', 'paid', 'price', 'in', 'on', 'at', 'to',
            'we', 'our', 'us', 'me', 'my', 'your', 'their', 'have', 'has', 'had', 'do', 'does', 'did',
        ];
        
        $out = [];
        foreach ($tokens as $token) {
            $token = trim((string) $token);
            if ($token === '' || in_array($token, $stopwords, true)) {
                continue;
            }
            if (strlen($token) <= 2 && $token !== 'ai') {
                continue;
            }
            $out[] = $token;
        }
        return array_values(array_unique($out));
    }

    /**
     * Safely checks if a string contains a specific keyword.
     */
    public function chatbot_text_contains_keyword($text, $keyword) {
        $text = strtolower((string) $text);
        $keyword = strtolower(trim((string) $keyword));
        
        if ($text === '' || $keyword === '') {
            return false;
        }
        if (strpos($keyword, ' ') !== false) {
            return strpos($text, $keyword) !== false;
        }
        if ((bool) preg_match('/^[\p{L}\p{N}\-]+$/u', $keyword)) {
            return (bool) preg_match('/(?<![\p{L}\p{N}])' . preg_quote($keyword, '/') . '(?![\p{L}\p{N}])/u', $text);
        }
        return strpos($text, $keyword) !== false;
    }

    /**
     * Calculates a bonus score based on how many keywords match the target text.
     */
    public function chatbot_keyword_overlap_bonus(array $keywords, $text) {
        if (empty($keywords)) {
            return 0.0;
        }
        $text = strtolower((string) $text);
        if ($text === '') {
            return 0.0;
        }
        
        $matches = 0;
        foreach ($keywords as $keyword) {
            if ($keyword !== '' && $this->chatbot_text_contains_keyword($text, $keyword)) {
                $matches++;
            }
        }
        if ($matches <= 0) {
            return 0.0;
        }
        
        $ratio = $matches / max(1, count($keywords));
        return min(0.22, $ratio * 0.22);
    }

    /**
     * Calculates a ranking bonus for events happening soon.
     */
    public function chatbot_upcoming_bonus($start_ts, $now_ts) {
        $start_ts = (int) $start_ts;
        $now_ts = (int) $now_ts;
        if ($start_ts <= 0 || $now_ts <= 0) {
            return 0.0;
        }
        if ($start_ts < $now_ts) {
            return -0.12; // Past event penalty
        }
        
        $days = ($start_ts - $now_ts) / DAY_IN_SECONDS;
        if ($days <= 3) return 0.12;
        if ($days <= 14) return 0.09;
        if ($days <= 30) return 0.06;
        if ($days <= 90) return 0.03;
        if ($days <= 180) return 0.0;
        
        return -0.02; // Far future slight penalty
    }

    /**
     * Calculates a ranking bonus based on how recently content was updated.
     */
    public function chatbot_content_recency_bonus($post_id, $now_ts) {
        $post_id = absint((int) $post_id);
        $now_ts = (int) $now_ts;
        if ($post_id <= 0 || $now_ts <= 0) {
            return 0.0;
        }
        
        $modified_ts = (int) get_post_modified_time('U', true, $post_id);
        if ($modified_ts <= 0) {
            $modified_ts = (int) get_post_time('U', true, $post_id);
        }
        if ($modified_ts <= 0) {
            return 0.0;
        }
        
        $age_days = max(0.0, ($now_ts - $modified_ts) / DAY_IN_SECONDS);
        if ($age_days <= 7) return 0.06;
        if ($age_days <= 30) return 0.04;
        if ($age_days <= 90) return 0.02;
        
        return 0.0;
    }

    /**
     * Evaluates a listing for AI-specific relevance (heuristics specifically for AI platform listings).
     */
    public function chatbot_ai_topic_match_data($post_id, $chunk_text, array $keywords = []) {
        $post_id = absint((int) $post_id);
        if ($post_id <= 0) {
            return ['is_match' => false, 'strength' => 0.0, 'reason' => ''];
        }

        $title = strtolower((string) get_the_title($post_id));
        $excerpt = strtolower((string) get_post_field('post_excerpt', $post_id));
        $content = strtolower((string) get_post_field('post_content', $post_id));
        
        // Use dynamically injected taxonomy slugs
        $type_terms = wp_get_post_terms($post_id, $this->tax_type, ['fields' => 'names']);
        $tag_terms = wp_get_post_terms($post_id, $this->tax_tag, ['fields' => 'names']);
        
        $type_text = strtolower(implode(' ', is_array($type_terms) ? $type_terms : []));
        $tag_text = strtolower(implode(' ', is_array($tag_terms) ? $tag_terms : []));
        $chunk_text = strtolower((string) $chunk_text);

        $ai_pattern = '/\b(ai|a\.i\.|artificial intelligence|machine learning|ml|deep learning|neural|nlp|llm|computer vision|robotics|data science|smart systems?)\b/u';
        $has_term_in_tags_or_type = (bool) preg_match($ai_pattern, $tag_text . ' ' . $type_text);
        $has_term_in_title = (bool) preg_match($ai_pattern, $title);
        $has_term_in_text = (bool) preg_match($ai_pattern, $excerpt . ' ' . $content);
        $has_term_in_chunk = (bool) preg_match($ai_pattern, $chunk_text);
        $keyword_bonus = $this->chatbot_keyword_overlap_bonus($keywords, $title . ' ' . $excerpt . ' ' . $content . ' ' . $tag_text . ' ' . $type_text . ' ' . $chunk_text);

        $strength = 0.0;
        if ($has_term_in_tags_or_type) $strength += 0.7;
        if ($has_term_in_title) $strength += 0.55;
        if ($has_term_in_text) $strength += 0.35;
        if ($has_term_in_chunk) $strength += 0.25;
        if ($keyword_bonus > 0.0) $strength += 0.25;

        $reason = '';
        if ($has_term_in_tags_or_type) {
            $reason = __('Matches AI tags/categories.', 'nexdirectory');
        } elseif ($has_term_in_title) {
            $reason = __('Mentions AI in the title.', 'nexdirectory');
        } elseif ($has_term_in_text || $has_term_in_chunk) {
            $reason = __('Mentions AI in the event details.', 'nexdirectory');
        }

        $strong_keyword_match = $keyword_bonus >= 0.12;
        $is_match = $has_term_in_tags_or_type
            || ($has_term_in_title && ($has_term_in_text || $has_term_in_chunk || $strong_keyword_match))
            || ($strong_keyword_match && ($has_term_in_text || $has_term_in_chunk));

        return [
            'is_match' => $is_match && $strength >= 0.6,
            'strength' => (float) $strength,
            'reason' => $reason,
        ];
    }

    /**
     * Evaluates general content topics against the generated AI knowledge catalog.
     */
    public function chatbot_content_topic_match_data($post_id, $chunk_text, $query_lc = '', array $keywords = []) {
        $post_id = absint((int) $post_id);
        if ($post_id <= 0) {
            return ['is_match' => false, 'boost' => 0.0, 'reason' => ''];
        }
        
        $post_type = sanitize_key((string) get_post_type($post_id));
        $title = strtolower((string) get_the_title($post_id));
        $excerpt = strtolower((string) get_post_field('post_excerpt', $post_id));
        $content = strtolower((string) get_post_field('post_content', $post_id));
        $chunk_text = strtolower((string) $chunk_text);
        $query_lc = strtolower((string) $query_lc);
        
        $catalog_match = $this->chatbot_match_query_with_catalog($query_lc);
        $catalog_terms = array_values(array_filter(array_map('eep_sanitize_text', (array) ($catalog_match['matched_terms'] ?? []))));

        $taxonomy_blob = '';
        if ($post_type !== '') {
            $taxonomy_names = get_object_taxonomies($post_type, 'names');
            if (is_array($taxonomy_names)) {
                $taxonomy_terms = [];
                foreach ($taxonomy_names as $taxonomy_name) {
                    $term_names = wp_get_post_terms($post_id, (string) $taxonomy_name, ['fields' => 'names']);
                    if (is_wp_error($term_names) || !is_array($term_names)) continue;
                    
                    foreach ($term_names as $term_name) {
                        $term_name = sanitize_text_field((string) $term_name);
                        if ($term_name !== '') $taxonomy_terms[] = strtolower($term_name);
                    }
                }
                if (!empty($taxonomy_terms)) {
                    $taxonomy_blob = implode(' ', array_values(array_unique($taxonomy_terms)));
                }
            }
        }

        $haystack = trim(implode(' ', array_filter([$title, $excerpt, $content, $chunk_text, $taxonomy_blob])));
        if ($haystack === '') {
            return ['is_match' => false, 'boost' => 0.0, 'reason' => ''];
        }

        $wants_clients = (bool) preg_match('/\b(client|clients|customer|customers)\b/u', $query_lc);
        $wants_case_studies = (bool) preg_match('/\b(case\s+stud(?:y|ies)|success\s+stor(?:y|ies)|portfolio|project|projects|examples?)\b/u', $query_lc);
        $wants_partners = (bool) preg_match('/\b(partner|partners|alliance|alliances)\b/u', $query_lc);
        $wants_posts = (bool) preg_match('/\b(post|posts|article|articles|blog|blogs|resource|resources|documentation|docs?|report|reports)\b/u', $query_lc);

        $strength = 0.0;
        $reason = '';

        if ($wants_clients && (bool) preg_match('/\b(client|clients|customer|customers)\b/u', $haystack)) {
            $strength += 0.4;
            $reason = __('Matches client-related content.', 'nexdirectory');
        }
        if ($wants_case_studies && (bool) preg_match('/\b(case\s+stud(?:y|ies)|success\s+stor(?:y|ies)|portfolio|project|projects|examples?)\b/u', $haystack)) {
            $strength += 0.45;
            if ($reason === '') $reason = __('Matches case study content.', 'nexdirectory');
        }
        if ($wants_partners && (bool) preg_match('/\b(partner|partners|alliance|alliances)\b/u', $haystack)) {
            $strength += 0.45;
            if ($reason === '') $reason = __('Matches partner content.', 'nexdirectory');
        }
        if ($wants_posts) {
            $post_like = in_array($post_type, ['post', 'page'], true) || (bool) preg_match('/\b(post|article|blog|news|insight|resource|documentation|report)\b/u', $haystack);
            if ($post_like) {
                $strength += 0.32;
                if ($reason === '') $reason = __('Matches your content request.', 'nexdirectory');
            }
        }

        $catalog_term_hits = 0;
        foreach ($catalog_terms as $catalog_term) {
            $catalog_term = strtolower(sanitize_text_field((string) $catalog_term));
            if ($catalog_term !== '' && $this->chatbot_text_contains_keyword($haystack, $catalog_term)) {
                $catalog_term_hits++;
            }
            if ($catalog_term_hits >= 5) break;
        }
        
        if ($catalog_term_hits > 0) {
            $strength += min(0.55, 0.15 + ($catalog_term_hits * 0.1));
            if ($reason === '') $reason = __('Matches indexed content categories.', 'nexdirectory');
        }

        $keyword_bonus = $this->chatbot_keyword_overlap_bonus($keywords, $haystack);
        if ($keyword_bonus > 0.0) {
            $strength += min(0.35, $keyword_bonus + 0.08);
            if ($reason === '') $reason = __('Matches your keywords.', 'nexdirectory');
        }

        $requested_specific_content = $wants_clients || $wants_case_studies || $wants_partners || $wants_posts || !empty($catalog_terms);
        $is_match = $requested_specific_content ? $strength >= 0.25 : ($keyword_bonus > 0.0 || $strength >= 0.2);

        return [
            'is_match' => $is_match,
            'boost' => min(0.28, max(0.0, $strength)),
            'reason' => $reason,
        ];
    }

    /**
     * Compares a query against the cached knowledge catalog terms.
     */
    public function chatbot_match_query_with_catalog($query_lc, array $catalog = []) {
        $query_lc = strtolower(trim((string) $query_lc));
        if ($query_lc === '') {
            return ['content_match' => false, 'matched_terms' => []];
        }
        
        if (empty($catalog)) {
            if (!is_object($this->knowledge_service) || !method_exists($this->knowledge_service, 'chatbot_get_knowledge_catalog')) {
                return ['content_match' => false, 'matched_terms' => []];
            }
            $catalog = $this->knowledge_service->chatbot_get_knowledge_catalog();
        }

        $terms = [];
        foreach ((array) ($catalog['content_types'] ?? []) as $row) {
            if (!is_array($row)) continue;
            
            $name = strtolower(sanitize_text_field((string) ($row['name'] ?? '')));
            if ($name !== '') $terms[] = $name;
            
            foreach ((array) ($row['aliases'] ?? []) as $alias) {
                $alias = strtolower(sanitize_text_field((string) $alias));
                if ($alias !== '') $terms[] = $alias;
            }
        }
        
        foreach ((array) ($catalog['topics'] ?? []) as $topic) {
            $topic = strtolower(sanitize_text_field((string) $topic));
            if ($topic !== '') $terms[] = $topic;
        }
        foreach ((array) ($catalog['user_ask_patterns'] ?? []) as $pattern) {
            $pattern = strtolower(sanitize_text_field((string) $pattern));
            if ($pattern !== '') $terms[] = $pattern;
        }
        
        $terms = array_values(array_unique(array_filter($terms)));
        if (empty($terms)) {
            return ['content_match' => false, 'matched_terms' => []];
        }

        $matched_terms = [];
        foreach ($terms as $term) {
            if ($term !== '' && $this->chatbot_text_contains_keyword($query_lc, $term)) {
                $matched_terms[] = $term;
            }
            if (count($matched_terms) >= 8) break;
        }
        
        return [
            'content_match' => !empty($matched_terms),
            'matched_terms' => array_values(array_unique(array_map('eep_sanitize_text', $matched_terms))),
        ];
    }

    /**
     * Determines aliases for popular geolocations.
     */
    public function chatbot_location_match_variants($location) {
        $location = strtolower(trim((string) $location));
        if ($location === '') {
            return [];
        }
        $variants = [$location];
        
        if ($location === 'uk' || $location === 'united kingdom') {
            $variants = array_merge($variants, ['uk', 'united kingdom', 'great britain', 'england', 'scotland', 'wales', 'northern ireland']);
        } elseif ($location === 'usa' || $location === 'u.s.a.' || $location === 'united states') {
            $variants = array_merge($variants, ['usa', 'u.s.a.', 'united states', 'united states of america', 'us', 'u.s.']);
        }
        
        return array_values(array_unique(array_map('trim', $variants)));
    }

    /**
     * Checks if a specific post's location data matches the user's location hint.
     */
    public function chatbot_row_matches_location($post_id, $chunk_text, $location_hint) {
        $location_hint = strtolower(trim((string) $location_hint));
        if ($location_hint === '') {
            return true;
        }
        
        $post_id = absint((int) $post_id);
        if ($post_id <= 0) {
            return false;
        }
        
        $city = strtolower(sanitize_text_field((string) get_post_meta($post_id, 'eep_city', true)));
        $country = strtolower(sanitize_text_field((string) get_post_meta($post_id, 'eep_country', true)));
        $venue = strtolower(sanitize_text_field((string) get_post_meta($post_id, 'eep_venue', true)));
        $address = strtolower(sanitize_text_field((string) get_post_meta($post_id, 'eep_address', true)));
        
        $haystack = trim(implode(' ', array_filter([
            $city,
            $country,
            $venue,
            $address,
            strtolower((string) $chunk_text),
        ])));
        
        if ($haystack === '') {
            return false;
        }
        
        $variants = $this->chatbot_location_match_variants($location_hint);
        foreach ($variants as $variant) {
            if ($variant !== '' && $this->chatbot_text_contains_keyword($haystack, $variant)) {
                return true;
            }
        }
        
        return false;
    }
}
