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

/**
 * Service for managing conversational context, user intent parsing, and transient storage.
 */
class EEP_Chatbot_State_Service {

    /**
     * @var EEP_Chatbot_Ranking_Service
     */
    private $ranking_service;

    /**
     * Constructor.
     *
     * @param EEP_Chatbot_Ranking_Service $ranking_service Required for matching intents against the AI catalog.
     */
    public function __construct($ranking_service) {
        $this->ranking_service = $ranking_service;
    }

    /**
     * Generates a unique transient key for the current user's chatbot session.
     */
    public function chatbot_get_state_transient_key() {
        $user_id = get_current_user_id();
        if ($user_id > 0) {
            return 'eep_chatbot_state_u_' . (string) $user_id;
        }
        $ip = sanitize_text_field((string) ($_SERVER['REMOTE_ADDR'] ?? ''));
        $ua = sanitize_text_field((string) ($_SERVER['HTTP_USER_AGENT'] ?? ''));
        return 'eep_chatbot_state_g_' . substr(md5($ip . '|' . $ua), 0, 24);
    }

    /**
     * Determines the TTL (Time to Live) for a chatbot session state.
     */
    public function chatbot_state_ttl_seconds() {
        $default_ttl = 12 * HOUR_IN_SECONDS;
        $ttl = (int) apply_filters('eep_chatbot_state_ttl_seconds', $default_ttl);
        $min_ttl = 15 * MINUTE_IN_SECONDS;
        $max_ttl = 7 * DAY_IN_SECONDS;
        
        if ($ttl < $min_ttl) $ttl = $min_ttl;
        if ($ttl > $max_ttl) $ttl = $max_ttl;
        
        return $ttl;
    }

    /**
     * Loads the active chatbot state for the current session.
     */
    public function chatbot_load_state() {
        $raw = get_transient($this->chatbot_get_state_transient_key());
        if (!is_array($raw)) {
            $raw = [];
        }
        
        $auto_search_enabled = true;
        if (array_key_exists('auto_search_enabled', $raw)) {
            $auto_search_enabled = in_array((string) $raw['auto_search_enabled'], ['1', 'true', 'yes', 'on'], true);
        }
        
        $last_results_raw = isset($raw['last_results']) && is_array($raw['last_results']) ? (array) $raw['last_results'] : [];
        $last_results = [];
        foreach ($last_results_raw as $event_id) {
            $event_id = absint((int) $event_id);
            if ($event_id > 0) {
                $last_results[] = $event_id;
            }
            if (count($last_results) >= 10) break;
        }
        
        return [
            'mode' => in_array((string) ($raw['mode'] ?? ''), ['chat', 'search'], true) ? sanitize_key((string) $raw['mode']) : 'chat',
            'last_query' => sanitize_text_field((string) ($raw['last_query'] ?? '')),
            'last_query_norm' => sanitize_text_field((string) ($raw['last_query_norm'] ?? '')),
            'last_shown_event_id' => absint((int) ($raw['last_shown_event_id'] ?? 0)),
            'last_chat_response_id' => sanitize_text_field((string) ($raw['last_chat_response_id'] ?? '')),
            'user_location_preference' => sanitize_text_field((string) ($raw['user_location_preference'] ?? '')),
            'user_format_preference' => sanitize_key((string) ($raw['user_format_preference'] ?? '')),
            'user_name' => sanitize_text_field((string) ($raw['user_name'] ?? '')),
            'last_results' => $last_results,
            'auto_search_enabled' => $auto_search_enabled,
            'awaiting_event_details' => !empty($raw['awaiting_event_details']) && in_array((string) $raw['awaiting_event_details'], ['1', 'true', 'yes', 'on'], true),
            'last_intent' => sanitize_key((string) ($raw['last_intent'] ?? '')),
            'updated_at' => sanitize_text_field((string) ($raw['updated_at'] ?? '')),
        ];
    }

    /**
     * Saves the chatbot state back to the transient cache.
     */
    public function chatbot_save_state(array $state) {
        $last_results_raw = isset($state['last_results']) && is_array($state['last_results']) ? (array) $state['last_results'] : [];
        $last_results = [];
        foreach ($last_results_raw as $event_id) {
            $event_id = absint((int) $event_id);
            if ($event_id > 0) {
                $last_results[] = $event_id;
            }
            if (count($last_results) >= 10) break;
        }
        
        $payload = [
            'mode' => in_array((string) ($state['mode'] ?? ''), ['chat', 'search'], true) ? sanitize_key((string) $state['mode']) : 'chat',
            'last_query' => sanitize_text_field((string) ($state['last_query'] ?? '')),
            'last_query_norm' => sanitize_text_field((string) ($state['last_query_norm'] ?? '')),
            'last_shown_event_id' => absint((int) ($state['last_shown_event_id'] ?? 0)),
            'last_chat_response_id' => sanitize_text_field((string) ($state['last_chat_response_id'] ?? '')),
            'user_location_preference' => sanitize_text_field((string) ($state['user_location_preference'] ?? '')),
            'user_format_preference' => sanitize_key((string) ($state['user_format_preference'] ?? '')),
            'user_name' => sanitize_text_field((string) ($state['user_name'] ?? '')),
            'last_results' => $last_results,
            'auto_search_enabled' => !empty($state['auto_search_enabled']) ? '1' : '0',
            'awaiting_event_details' => !empty($state['awaiting_event_details']) ? '1' : '0',
            'last_intent' => sanitize_key((string) ($state['last_intent'] ?? '')),
            'updated_at' => current_time('mysql'),
        ];
        
        set_transient($this->chatbot_get_state_transient_key(), $payload, $this->chatbot_state_ttl_seconds());
    }

    /**
     * Builds a safe metadata array representing the current state, for frontend consumption.
     */
    public function chatbot_build_assistant_state_meta(array $state = []) {
        return [
            'mode' => in_array((string) ($state['mode'] ?? ''), ['chat', 'search'], true) ? sanitize_key((string) $state['mode']) : 'chat',
            'last_query' => sanitize_text_field((string) ($state['last_query'] ?? '')),
            'last_results' => array_values(array_filter(array_map('absint', isset($state['last_results']) && is_array($state['last_results']) ? (array) $state['last_results'] : []), static function ($event_id) {
                return $event_id > 0;
            })),
            'user_name' => sanitize_text_field((string) ($state['user_name'] ?? '')),
            'preferred_location' => sanitize_text_field((string) ($state['user_location_preference'] ?? '')),
            'preferred_format' => sanitize_key((string) ($state['user_format_preference'] ?? '')),
            'last_chat_response_id' => sanitize_text_field((string) ($state['last_chat_response_id'] ?? '')),
            'awaiting_event_details' => !empty($state['awaiting_event_details']),
        ];
    }

    /**
     * Normalizes the conversation history array.
     */
    public function chatbot_normalize_history($history_raw) {
        $history = [];
        if (!is_array($history_raw)) return $history;
        
        $history_raw = array_slice($history_raw, -10);
        foreach ($history_raw as $row) {
            if (!is_array($row)) continue;
            
            $role = sanitize_key((string) ($row['role'] ?? ''));
            if (!in_array($role, ['user', 'assistant'], true)) continue;
            
            $content = eep_sanitize_textarea((string) ($row['content'] ?? ''));
            $content = trim($content);
            if ($content === '') continue;
            
            if (function_exists('mb_strlen') && mb_strlen($content) > 700) {
                $content = (string) mb_substr($content, 0, 700);
            } elseif (strlen($content) > 700) {
                $content = (string) substr($content, 0, 700);
            }
            
            $history[] = [
                'role' => $role,
                'content' => $content,
            ];
        }
        return $history;
    }

    /**
     * Converts history array into a text block for OpenAI prompting.
     */
    public function chatbot_history_to_prompt_block(array $history) {
        $lines = [];
        foreach ($history as $turn) {
            if (!is_array($turn)) continue;
            
            $role = sanitize_key((string) ($turn['role'] ?? ''));
            $content = trim((string) ($turn['content'] ?? ''));
            if ($content === '' || !in_array($role, ['user', 'assistant'], true)) continue;
            
            $label = $role === 'assistant' ? 'Assistant' : 'User';
            $lines[] = $label . ': ' . $content;
        }
        return empty($lines) ? '' : implode("\n", $lines);
    }

    /**
     * Normalizes a query specifically for state comparison tracking.
     */
    public function chatbot_normalize_query_for_state($query) {
        $query = strtolower(trim((string) $query));
        $query = (string) preg_replace('/\s+/u', ' ', $query);
        return trim($query);
    }

    /**
     * Natural language parser to detect date windows in queries.
     */
    public function chatbot_extract_date_window($query) {
        $query = strtolower((string) $query);
        $now = current_time('timestamp');
        
        if (preg_match('/\b(?:in\s+)?(20\d{2})\b/u', $query, $matches)) {
            $year = (int) ($matches[1] ?? 0);
            if ($year >= 2000 && $year <= 2100) {
                $start = strtotime($year . '-01-01 00:00:00');
                $end = strtotime($year . '-12-31 23:59:59');
                if ($start && $end) {
                    return ['from' => $start, 'to' => $end];
                }
            }
        }
        if (preg_match('/\bnext\s+(\d+)\s*(day|days|week|weeks|month|months)\b/u', $query, $matches)) {
            $count = max(1, min(365, (int) ($matches[1] ?? 1)));
            $unit = (string) ($matches[2] ?? 'days');
            $multiplier = DAY_IN_SECONDS;
            if (strpos($unit, 'week') === 0) {
                $multiplier = 7 * DAY_IN_SECONDS;
            } elseif (strpos($unit, 'month') === 0) {
                $multiplier = 30 * DAY_IN_SECONDS;
            }
            return [
                'from' => $now,
                'to' => $now + ($count * $multiplier),
            ];
        }
        if (strpos($query, 'today') !== false) {
            $start = strtotime('today', $now);
            return ['from' => $start, 'to' => $start + DAY_IN_SECONDS - 1];
        }
        if (strpos($query, 'tomorrow') !== false) {
            $start = strtotime('tomorrow', $now);
            return ['from' => $start, 'to' => $start + DAY_IN_SECONDS - 1];
        }
        if (strpos($query, 'this week') !== false) {
            $start = strtotime('monday this week', $now);
            return ['from' => $start, 'to' => $start + (7 * DAY_IN_SECONDS) - 1];
        }
        if (strpos($query, 'next week') !== false) {
            $start = strtotime('monday next week', $now);
            return ['from' => $start, 'to' => $start + (7 * DAY_IN_SECONDS) - 1];
        }
        if (strpos($query, 'this month') !== false) {
            $start = strtotime(date('Y-m-01 00:00:00', $now));
            return ['from' => $start, 'to' => strtotime('+1 month', $start) - 1];
        }
        if (strpos($query, 'next year') !== false) {
            $start = strtotime(date('Y-01-01 00:00:00', strtotime('+1 year', $now)));
            return ['from' => $start, 'to' => strtotime('+1 year', $start) - 1];
        }
        if (strpos($query, 'this year') !== false) {
            $start = strtotime(date('Y-01-01 00:00:00', $now));
            return ['from' => $start, 'to' => strtotime('+1 year', $start) - 1];
        }
        return null;
    }

    /**
     * Extracts geographic intent from a query.
     */
    public function chatbot_extract_location_hint($query, array $preferences = [], $allow_preference_fallback = true) {
        if ($allow_preference_fallback) {
            $pref_location = trim((string) ($preferences['location'] ?? ''));
            if ($pref_location !== '') return eep_sanitize_text($pref_location);
        }

        $query_lc = strtolower(trim((string) $query));
        if ($query_lc === '') return '';
        
        if (preg_match('/\b(online|virtual|webinar|stream|livestream|hybrid)\b/u', $query_lc)) {
            // Pure online preference is not a location, unless accompanied by "in"
            if (!preg_match('/\b(in|near|around|at|from)\s+/u', $query_lc)) {
                return '';
            }
        }

        if (preg_match('/\b(?:in|near|around|at|from)\s+([a-z][a-z\s\-]{1,45})$/iu', $query_lc, $matches)) {
            $candidate = trim((string) ($matches[1] ?? ''));
            $candidate = preg_replace('/\b(events?|listings?|please|thanks?|today|tomorrow|this week|next week)\b/iu', '', (string) $candidate);
            $candidate = trim((string) preg_replace('/\s{2,}/u', ' ', (string) $candidate));
            if ($candidate !== '' && strlen($candidate) >= 2) {
                return eep_sanitize_text($candidate);
            }
        }

        $known_locations = [
            'uk', 'united kingdom', 'england', 'scotland', 'wales', 'ireland',
            'usa', 'u.s.a.', 'united states', 'canada', 'australia', 'singapore',
            'india', 'europe', 'asia', 'africa',
            'london', 'manchester', 'birmingham', 'new york', 'seattle', 'paris', 'berlin', 'tokyo',
        ];
        foreach ($known_locations as $location) {
            if (preg_match('/\b' . preg_quote((string) $location, '/') . '\b/u', $query_lc)) {
                return sanitize_text_field((string) $location);
            }
        }

        return '';
    }

    /**
     * Natural language parser to detect if the user introduces their name.
     */
    public function chatbot_parse_user_name_statement($query) {
        $query = trim((string) $query);
        if ($query === '') return '';
        
        $patterns = [
            '/\bmy name is\s+([a-z][a-z\'\-]{1,39})\b/iu',
            '/\bi am\s+([a-z][a-z\'\-]{1,39})\b/iu',
            '/\bi\'m\s+([a-z][a-z\'\-]{1,39})\b/iu',
            '/\bcall me\s+([a-z][a-z\'\-]{1,39})\b/iu',
        ];
        
        foreach ($patterns as $pattern) {
            if (preg_match($pattern, $query, $matches)) {
                $name = trim(sanitize_text_field((string) ($matches[1] ?? '')));
                if ($name !== '') {
                    $blocked = ['looking', 'searching', 'finding', 'need', 'want', 'interested', 'after', 'from', 'for', 'in', 'online', 'events', 'event'];
                    if (in_array(strtolower($name), $blocked, true)) {
                        return '';
                    }
                    return eep_sanitize_text(ucfirst(strtolower($name)));
                }
            }
        }
        return '';
    }

    /**
     * Extracts online/in-person format hints.
     */
    public function chatbot_extract_format_hint($query, array $preferences = []) {
        $pref_mode = sanitize_key((string) ($preferences['mode'] ?? ''));
        if (in_array($pref_mode, ['online', 'in_person'], true)) {
            return $pref_mode;
        }
        
        $query_lc = strtolower(trim((string) $query));
        if ($query_lc === '') return '';
        
        if (preg_match('/\b(online|virtual|webinar|stream|livestream)\b/u', $query_lc)) {
            return 'online';
        }
        if (preg_match('/\b(in[-\s]?person|onsite|on-site)\b/u', $query_lc)) {
            return 'in_person';
        }
        return '';
    }

    /**
     * Parses the underlying query constraints and flags.
     */
    public function chatbot_parse_query_intent($query, array $preferences = []) {
        $query_raw = trim((string) $query);
        $query_lc = strtolower($query_raw);
        $word_count = 0;
        
        if (preg_match_all('/[\p{L}\p{N}\'-]+/u', $query_lc, $word_matches)) {
            $word_count = (int) count((array) ($word_matches[0] ?? []));
        }
        
        $pref_mode = sanitize_key((string) ($preferences['mode'] ?? ''));
        $pref_price = sanitize_key((string) ($preferences['price'] ?? ''));
        $pref_date = sanitize_key((string) ($preferences['date'] ?? ''));
        $pref_location = sanitize_text_field((string) ($preferences['location'] ?? ''));
        
        $has_date_explicit = is_array($this->chatbot_extract_date_window($query_lc))
            || (bool) preg_match('/\b(\d{1,2}[\/\-]\d{1,2}([\/\-]\d{2,4})?|\d{4}[\/\-]\d{1,2}[\/\-]\d{1,2}|20\d{2}|jan|feb|mar|apr|may|jun|jul|aug|sep|oct|nov|dec|this year|next year)\b/u', $query_lc);
        $has_format_explicit = (bool) preg_match('/\b(online|virtual|webinar|stream|livestream|in[-\s]?person|hybrid|onsite|on-site)\b/u', $query_lc);
        $has_price_explicit = (bool) preg_match('/\b(free|paid|premium|price|pricing|ticket|tickets|cost|budget|cheap)\b/u', $query_lc);
        $has_location_explicit = (bool) preg_match('/\b(in|near|around|at|from)\s+[a-z][\p{L}\s\-]{1,40}\b/u', $query_lc)
            || (bool) preg_match('/\b(uk|u\.k\.|usa|u\.s\.a\.|united kingdom|united states|europe|asia|africa|australia|canada|india|london|singapore|paris|berlin|tokyo|new york)\b/u', $query_lc);
            
        $has_date_pref = $pref_date !== '';
        $has_format_pref = in_array($pref_mode, ['online', 'in_person'], true);
        $has_price_pref = in_array($pref_price, ['free', 'paid'], true);
        $has_location_pref = $pref_location !== '';
        
        $has_date = $has_date_explicit || $has_date_pref;
        $has_format = $has_format_explicit || $has_format_pref;
        $has_price = $has_price_explicit || $has_price_pref;
        $has_location = $has_location_explicit || $has_location_pref;
        
        $wants_more = (bool) preg_match('/\b(show\s+more|more\s+options|more\s+results|list\s+all|all\s+events|all\s+options|show\s+all|another\s+option|more\s+like\s+this)\b/u', $query_lc);
        if (!$wants_more && $word_count <= 4) {
            $wants_more = (bool) preg_match('/\b(more|list|all|options?)\b/u', $query_lc);
        }
        
        $is_ai_topic = (bool) preg_match('/\b(ai|a\.i\.|artificial intelligence|machine learning|ml|deep learning)\b/u', $query_lc);
        $has_event_terms = (bool) preg_match('/\b(event|events|listing|listings|conference|summit|webinar|workshop|meetup|expo|session|agenda|itinerary|ticket|tickets|rsvp)\b/u', $query_lc);
        $has_search_terms = (bool) preg_match('/\b(find|search|show|recommend|suggest|looking\s+for|need|want|anything|something|list|examples?|what(?:\'?s|\s+is)\s+on)\b/u', $query_lc);
        $has_content_terms = (bool) preg_match('/\b(client|clients|customer|customers|case\s+stud(?:y|ies)|success\s+stor(?:y|ies)|story|stories|partner|partners|portfolio|project|projects|examples?|post|posts|article|articles|blog|blogs|resource|resources|whitepaper|whitepapers|report|reports|documentation|docs?)\b/u', $query_lc);
        
        $catalog_match = $this->ranking_service->chatbot_match_query_with_catalog($query_lc);
        $catalog_has_content_match = !empty($catalog_match['content_match']);
        $catalog_terms = array_values(array_filter(array_map('eep_sanitize_text', (array) ($catalog_match['matched_terms'] ?? []))));
        if ($catalog_has_content_match) {
            $has_content_terms = true;
        }

        $location_hint = eep_sanitize_text($this->chatbot_extract_location_hint($query_raw, [], false));
        if ($location_hint !== '') {
            $has_location = true;
            $has_location_explicit = true;
        }
        
        $has_constraints_explicit = $has_location_explicit || $has_date_explicit || $has_format_explicit || $has_price_explicit;
        $has_constraints_pref = !$has_constraints_explicit && ($has_location_pref || $has_date_pref || $has_format_pref || $has_price_pref);
        $no_constraints = !$has_location && !$has_date && !$has_format && !$has_price;
        $is_broad = $word_count <= 6 || $no_constraints;

        return [
            'word_count' => $word_count,
            'has_date' => $has_date,
            'has_date_explicit' => $has_date_explicit,
            'has_date_pref' => $has_date_pref,
            'has_format' => $has_format,
            'has_format_explicit' => $has_format_explicit,
            'has_format_pref' => $has_format_pref,
            'has_price' => $has_price,
            'has_price_explicit' => $has_price_explicit,
            'has_price_pref' => $has_price_pref,
            'has_location' => $has_location,
            'has_location_explicit' => $has_location_explicit,
            'has_location_pref' => $has_location_pref,
            'has_constraints_explicit' => $has_constraints_explicit,
            'has_constraints_pref' => $has_constraints_pref,
            'no_constraints' => $no_constraints,
            'is_broad' => $is_broad,
            'wants_more' => $wants_more,
            'is_ai_topic' => $is_ai_topic,
            'has_event_terms' => $has_event_terms,
            'has_search_terms' => $has_search_terms,
            'has_content_terms' => $has_content_terms,
            'catalog_content_match' => $catalog_has_content_match,
            'catalog_terms' => $catalog_terms,
            'location_hint' => $location_hint,
            'query_lc' => $query_lc,
        ];
    }

    /**
     * Categorizes a user query into a primary intent mode.
     */
    public function chatbot_classify_intent($query, array $intent_data = [], array $history = [], array $state = [], array $preferences = []) {
        $query_lc = strtolower(trim((string) $query));
        if ($query_lc === '') return 'unknown';
        
        $normalized = $this->chatbot_normalize_query_for_state($query_lc);
        $word_count = 0;
        if (preg_match_all('/[\p{L}\p{N}\'-]+/u', $normalized, $word_matches)) {
            $word_count = (int) count((array) ($word_matches[0] ?? []));
        }
        
        $has_previous_topic = trim((string) ($state['last_query'] ?? '')) !== '';
        $has_event_terms = !empty($intent_data['has_event_terms']);
        $has_search_terms = !empty($intent_data['has_search_terms']);
        $has_content_terms = !empty($intent_data['has_content_terms']);
        $current_mode = in_array((string) ($state['mode'] ?? ''), ['chat', 'search'], true) ? (string) $state['mode'] : 'chat';
        $awaiting_details = !empty($state['awaiting_event_details']);
        $has_active_search_context = $current_mode === 'search' || $awaiting_details;
        
        $has_topic_terms = !empty($intent_data['is_ai_topic']) || (bool) preg_match('/\b(robotics|automation|software|data|ml|machine learning|artificial intelligence|smart systems?|civil|mechanical|electrical|energy|sustainability)\b/u', $normalized);
        $has_constraints_explicit = !empty($intent_data['has_constraints_explicit']);
        $has_format_constraint = !empty($intent_data['has_format_explicit']);
        $has_price_constraint = !empty($intent_data['has_price_explicit']);
        $has_location = !empty($intent_data['has_location_explicit']) || trim((string) ($intent_data['location_hint'] ?? '')) !== '';

        if ((bool) preg_match('/^(?:i\s+meant\s+)?(hi+|hello+|hey+|yo+|hiya+|ho+|good\s+(morning|afternoon|evening)|morning|afternoon|evening)[\s\.\!\?]*$/u', $normalized) && $word_count <= 6) {
            return 'greeting';
        }
        if ((bool) preg_match('/\b(who are you|what are you|what can you do|what do you do|are you a bot|are you ai|your name)\b/u', $normalized)) {
            return 'identity_question';
        }
        if ((bool) preg_match('/^(stop|stop it|enough|pause|leave it|leave this)[\s\.\!\?]*$/u', $normalized) || (bool) preg_match('/\b(i\s+did(?:n\'t| not)\s+ask\s+for\s+(?:an?\s+event|that|this)|don\'?t\s+(?:search|show)|stop\s+(?:searching|showing)|no\s+more\s+(?:results?|events?))\b/u', $normalized)) {
            return 'stop_command';
        }
        if ($this->chatbot_parse_user_name_statement($query) !== '') {
            return 'user_identity_statement';
        }
        if ((bool) preg_match('/\b(how\s+are\s+you|how\'?s\s+it\s+going|what\'?s\s+up|how\s+(?:is|was)\s+your\s+day|how\s+are\s+things|favo(?:u)?rite\s+colou?r|what\s+colou?r\s+do\s+you\s+like|how\s+old\s+are\s+you|i\s+asked\s+(?:your|about\s+your)\s+age|tell\s+me\s+about\s+yourself|i(?:\s+am|\'?m)\s+tired|sleepy|do\s+you\s+sleep|do\s+u\s+sleep|do\s+you\s+slep|do\s+u\s+slep)\b/u', $normalized) && !$has_event_terms && !$has_search_terms && !$has_constraints_explicit && !$has_topic_terms) {
            return 'small_talk';
        }
        if ((bool) preg_match('/\b(i need a friend|need a friend|will you be my f(?:riend|irend)|be my f(?:riend|irend)|i feel alone|i am alone|i\'m alone|lonely)\b/u', $normalized)) {
            return 'personal_support';
        }
        if ((bool) preg_match('/\b(is that a yes)\b/u', $normalized) && (string) ($state['last_intent'] ?? '') === 'personal_support') {
            return 'personal_support';
        }
        if ((bool) preg_match('/\b(is that a (?:yes|no)|so (?:yes|no)\??)\b/u', $normalized) && in_array((string) ($state['last_intent'] ?? ''), ['small_talk', 'personal_support', 'clarification', 'unknown'], true)) {
            return 'small_talk';
        }
        if ((bool) preg_match('/\b(what(\'?s|\sis)?\s+wrong\s+with\s+you|you(\'re|\sare)?\s+wrong|broken|useless|stupid|idiot|wtf|this\s+isn\'?t\s+working|you\s+don\'?t\s+understand|nonsense|you\s+keep\s+getting\s+it\s+wrong|that\'?s\s+not\s+right|why\s+do\s+you\s+ignore\s+me|brick wall)\b/u', $normalized)) {
            return 'frustration';
        }
        if (((bool) preg_match('/^(what|huh|hmm|why|how|which|explain|clarify|\?)$/u', trim($normalized, " \t\n\r\0\x0B.!?")) || (bool) preg_match('/\b(not sure|not that|i mean|what do you mean|can you clarify)\b/u', $normalized)) && !$has_event_terms && !$has_search_terms) {
            return 'clarification';
        }
        if ((bool) preg_match('/\b(thanks|thank you|ok|okay|cool|nice|great|got it|understood|fine|alright)\b/u', $normalized) && !$has_event_terms && !$has_search_terms && !$has_constraints_explicit) {
            return 'small_talk';
        }

        $location_discovery_query = $has_location && (bool) preg_match('/\b(something|anything|what(?:\'?s|\s+is)\s+on|recommend)\b/u', $normalized) && !$has_topic_terms && !$has_format_constraint && !$has_price_constraint;
        if ($location_discovery_query) return 'event_query';

        if ((bool) preg_match('/\b([a-z]{2,}(?:\s[a-z]{2,})?)\s+(events?|listings?)\b/u', $normalized) && ($has_event_terms || $has_location)) {
            return 'event_query';
        }
        if (!empty($intent_data['has_date_explicit']) && (bool) preg_match('/\b(anything|something|events?|listings?|what(?:\'?s|\s+is)\s+on|upcoming)\b/u', $normalized)) {
            return 'event_query';
        }

        $content_query = !$has_event_terms && $has_content_terms && ($has_search_terms || $word_count <= 8 || (bool) preg_match('/\b(what|which|any|list|show|find|examples?)\b/u', $normalized));
        if ($content_query) return 'content_query';

        if ($has_previous_topic && $has_active_search_context && in_array((string) ($state['last_intent'] ?? ''), ['content_query', 'refinement'], true) && !$has_event_terms && !$has_search_terms && !$has_constraints_explicit && !$has_topic_terms && $word_count <= 5) {
            return 'refinement';
        }
        if ($has_event_terms || $has_topic_terms) return 'event_query';

        $filter_only = (bool) preg_match('/\b(online|in[-\s]?person|hybrid|free|paid|this week|next week|next 14 days|later this year|near me|more|show more|list all|all options|show all|options?)\b/u', $normalized) && !$has_event_terms && $word_count <= 7;
        
        if ($has_previous_topic && $has_active_search_context && !$has_event_terms && !$has_topic_terms && ($filter_only || $has_constraints_explicit || (!empty($intent_data['wants_more']) && $word_count <= 8))) {
            return 'refinement';
        }
        if ($has_location && !$has_previous_topic && !$has_event_terms && !$has_search_terms && !$has_topic_terms && $word_count <= 8) return 'event_query';
        if ($filter_only && !$has_previous_topic) return 'unknown';

        if ($has_previous_topic && $has_active_search_context && in_array((string) ($state['last_intent'] ?? ''), ['event_query', 'refinement'], true) && !$has_event_terms && !$has_search_terms && !$has_constraints_explicit && !$has_topic_terms && !$has_content_terms && !$has_location && $word_count <= 8) {
            return 'unknown';
        }
        if ($word_count <= 2 && !$has_event_terms && !$has_search_terms && !$has_constraints_explicit && !$has_topic_terms) return 'unknown';
        if ($has_constraints_explicit) return 'event_query';
        if ($has_search_terms && $has_previous_topic && $has_active_search_context) return 'refinement';
        if ($has_search_terms && $has_content_terms) return 'content_query';

        return 'unknown';
    }
}
