/**
 * Chat Store (Zustand)
 * Central state management for the chat widget
 */

import { create } from 'zustand';
import { persist, createJSONStorage } from 'zustand/middleware';
import type { ChatMessage, ChatConfig, Widget, SendMessageOptions, WooCart } from '@/types';
import { chatApi, RateLimitError } from '@/api/chat';
import { generateVisitorId } from '@/utils/visitor';
import { orchestrator, type OrchestratorState } from '@/lib/orchestrator';
import { proactiveTimer } from '@/services/ProactiveTimerManager';
import { getCartTelemetryFields, trackCommerceEvent } from '@/services/commerceTelemetry';
import { t } from '@/lib/i18n';
import { cartDebugLog, getWooCartCount } from '@/utils/cart';
import { getCurrentLocale, getStorageKey, migrateUnscopedStorage } from '@/utils/locale';
import type { CommerceContextLite } from '@/types';

const SEARCH_TRANSITION_CONTEXT_SESSION_KEY = 'boostbot-search-transition-context';
const SEARCH_TRANSITION_MAX_AGE_MS = 2 * 60 * 1000;

// Migrate unscoped storage keys before the store rehydrates from localStorage.
migrateUnscopedStorage();

// Locale-scoped storage wrapper: the persist middleware key 'boostbot-chat'
// is transparently suffixed with the current language code so each language
// gets its own conversation state (e.g. 'boostbot-chat_en', 'boostbot-chat_he').
const localeScopedStorage = createJSONStorage(() => ({
  getItem: (name: string): string | null =>
    localStorage.getItem(`${name}_${getCurrentLocale()}`),
  setItem: (name: string, value: string): void => {
    localStorage.setItem(`${name}_${getCurrentLocale()}`, value);
  },
  removeItem: (name: string): void => {
    localStorage.removeItem(`${name}_${getCurrentLocale()}`);
  },
}));

interface ChatState {
  // UI State
  isOpen: boolean;
  isTyping: boolean;
  isStreaming: boolean; // Performance optimization: SSE streaming mode
  streamingContent: string; // Content being streamed
  unreadCount: number;
  isLoaded: boolean;
  isThinking: boolean; // Gemini orchestrator is analyzing
  thinkingMessage: string | null;
  
  // Orchestrator state (Phase 2)
  orchestratorState: OrchestratorState;
  isUserTyping: boolean; // Tracks if user is actively typing in input
  inputFocused: boolean; // Tracks whether input is focused (user engagement)
  lastUserTypedAt: number; // Epoch ms timestamp of last typing activity
  
  // Per-visitor message rate limiting (Week 33)
  cooldownUntil: number | null; // Epoch ms when cooldown expires
  remainingHour: number | null;
  remainingDay: number | null;
  
  // Config
  config: ChatConfig;
  currentBotId: number | null; // Track current bot to detect changes
  previousBotId: number | null; // Track previous bot for page transition triggers
  previousPageUrl: string | null; // Track previous page URL for context
  
  // Widget assignments from last retrieval (for proactive/page-transition guard)
  lastWidgetAssignments: Array<{ type: string; id?: number }> | null;
  
  // Session
  visitorId: string;
  conversationId: string | null;
  
  // Messages
  messages: ChatMessage[];
  
  // Widgets
  activeWidget: Widget | null;

  // WooCommerce cart
  cart: WooCart | null;
  isCartOpen: boolean;
  isCartLoading: boolean;
  cartError: string | null;
  lastBundleCouponCode: string | null;
  
  // Triggers - track which triggers have fired
  firedTriggers: Set<string>;
  
  // Actions
  toggleChat: () => void;
  openChat: () => void;
  closeChat: () => void;
  openWithMessage: (message: string, triggerId?: string) => void;
  sendMessage: (content: string, options?: SendMessageOptions) => Promise<void>;
  loadConfig: () => Promise<void>;
  setActiveWidget: (widget: Widget | null) => void;
  clearChat: () => void;
  addMessage: (message: ChatMessage) => void;
  addScriptedExchange: (question: string, answer: string) => void;
  refreshCart: () => Promise<WooCart | null>;
  setCartState: (cart: WooCart | null) => void;
  addToCartAndSync: (params: {
    productId: number;
    quantity?: number;
    variationId?: number;
    variation?: Record<string, string>;
  }) => Promise<{ success: boolean; cart: WooCart | null; message?: string; error?: string }>;
  updateCartItemAndSync: (itemKey: string, quantity: number) => Promise<{ success: boolean; cart: WooCart | null; message?: string; error?: string }>;
  removeCartItemAndSync: (itemKey: string) => Promise<{ success: boolean; cart: WooCart | null; message?: string; error?: string }>;
  applyCouponAndSync: (code: string) => Promise<{ success: boolean; cart: WooCart | null; message?: string; discountDisplay?: string; error?: string }>;
  removeCouponAndSync: (code: string) => Promise<{ success: boolean; cart: WooCart | null; message?: string; error?: string }>;
  setLastBundleCouponCode: (code: string | null) => void;
  openCart: () => void;
  closeCart: () => void;
  toggleCart: () => void;
  markTriggerFired: (triggerId: string) => void;
  hasTriggerFired: (triggerId: string) => boolean;
  updateStreamingContent: (content: string) => void;
  setThinking: (message: string | null) => void;
  
  // Orchestrator actions (Phase 2)
  userStartedTyping: () => void;
  userStoppedTyping: () => void;
  setInputFocused: (focused: boolean) => void;
  initOrchestrator: () => void;
  
  // Page transition trigger
  firePageTransitionTrigger: (params: {
    previousBotId: number;
    newBotId: number;
    previousPageUrl: string;
    triggerReason?: 'bot_change' | 'search_redirect';
    searchQuery?: string;
  }) => void;
}

// Default config
const defaultConfig: ChatConfig = {
  bot_name: 'BoostBot',
  welcome_message: '',
  show_welcome_message: true,
  primary_color: '#3BC2D0',
  position: 'bottom-right',
  avatar_url: null,
  show_on_mobile: true,
  auto_open_delay: 0,
  widgets_enabled: [],
  // Week 28 defaults
  visitor_input_char_limit_enabled: true,
  visitor_input_char_limit: 500,
  bot_output_char_limit_global: 800,
  bot_output_char_limit: 800,
};

interface PendingSearchTransitionContext {
  query: string;
  sourceUrl: string;
  destinationUrl: string;
  submittedAt: number;
}

function normalizeUrlForComparison(url: string): string {
  try {
    const parsed = new URL(url, window.location.href);
    parsed.hash = '';
    return parsed.toString();
  } catch {
    return url;
  }
}

function readPendingSearchTransitionContext(): PendingSearchTransitionContext | null {
  if (typeof window === 'undefined') {
    return null;
  }

  const raw = sessionStorage.getItem(SEARCH_TRANSITION_CONTEXT_SESSION_KEY);
  if (!raw) {
    return null;
  }

  try {
    const parsed = JSON.parse(raw) as Partial<PendingSearchTransitionContext>;
    if (
      typeof parsed.query !== 'string'
      || typeof parsed.sourceUrl !== 'string'
      || typeof parsed.destinationUrl !== 'string'
      || typeof parsed.submittedAt !== 'number'
    ) {
      sessionStorage.removeItem(SEARCH_TRANSITION_CONTEXT_SESSION_KEY);
      return null;
    }

    if (!parsed.query.trim()) {
      sessionStorage.removeItem(SEARCH_TRANSITION_CONTEXT_SESSION_KEY);
      return null;
    }

    if (Date.now() - parsed.submittedAt > SEARCH_TRANSITION_MAX_AGE_MS) {
      sessionStorage.removeItem(SEARCH_TRANSITION_CONTEXT_SESSION_KEY);
      return null;
    }

    if (normalizeUrlForComparison(window.location.href) !== normalizeUrlForComparison(parsed.destinationUrl)) {
      return null;
    }

    return {
      query: parsed.query.trim(),
      sourceUrl: parsed.sourceUrl,
      destinationUrl: parsed.destinationUrl,
      submittedAt: parsed.submittedAt,
    };
  } catch {
    sessionStorage.removeItem(SEARCH_TRANSITION_CONTEXT_SESSION_KEY);
    return null;
  }
}

function clearPendingSearchTransitionContext() {
  if (typeof window === 'undefined') {
    return;
  }

  sessionStorage.removeItem(SEARCH_TRANSITION_CONTEXT_SESSION_KEY);
}

/**
 * Simulate SSE-like word-by-word streaming for scripted/pre-written messages.
 * Creates a natural typing effect without any real API call.
 */
function simulateScriptedStream(
  set: (fn: (state: ChatState) => Partial<ChatState>) => void,
  messageId: string,
  fullContent: string,
  intervalMs = 30,
  options?: { notifyTimerOnComplete?: boolean },
) {
  // Split into tokens (words + whitespace) to stream word-by-word
  const tokens = fullContent.split(/(\s+)/);
  let revealed = '';
  let idx = 0;

  const tick = () => {
    // Reveal 1–3 tokens per tick for natural variation (like real SSE)
    const count = 1 + Math.floor(Math.random() * 2);
    for (let i = 0; i < count && idx < tokens.length; i++) {
      revealed += tokens[idx++];
    }

    set((state) => ({
      messages: state.messages.map((m) =>
        m.id === messageId ? { ...m, content: revealed, isStreaming: true } : m
      ),
    }));

    if (idx >= tokens.length) {
      // Done — set final content and clear streaming flag
      set((state) => ({
        messages: state.messages.map((m) =>
          m.id === messageId ? { ...m, content: fullContent, isStreaming: false } : m
        ),
        isStreaming: false,
      }));
      if (options?.notifyTimerOnComplete) {
        proactiveTimer.onSSEComplete();
      }
    } else {
      setTimeout(tick, intervalMs + Math.random() * 20);
    }
  };

  // Start after a brief "thinking" pause
  setTimeout(tick, 300);
}

function getWooCommerceConfig(state?: Pick<ChatState, 'config'> | null) {
  return state?.config?.woocommerce ?? window.boostbot_config?.woocommerce;
}

function isWooCommerceEnabled(state?: Pick<ChatState, 'config'> | null): boolean {
  return getWooCommerceConfig(state)?.active === true;
}

function isCartButtonEnabled(state?: Pick<ChatState, 'config'> | null): boolean {
  const woocommerce = getWooCommerceConfig(state);
  return woocommerce?.active === true && woocommerce?.show_cart_button === true;
}

function buildCartContextLite(
  cart: WooCart | null | undefined,
  state?: Pick<ChatState, 'config'> | null,
): CommerceContextLite | undefined {
  const woocommerce = getWooCommerceConfig(state);
  if (!woocommerce?.active) {
    return undefined;
  }

  const cartCount = getWooCartCount(cart);

  if (!cart || cart.is_empty || cartCount <= 0) {
    return undefined;
  }

  const cartContext: CommerceContextLite = {
    item_count: cartCount,
    subtotal_display: cart.total || cart.subtotal,
    currency: cart.currency_code,
    currency_symbol: cart.currency_symbol || woocommerce.currency_symbol,
    is_cart_page: woocommerce.is_cart,
    is_checkout_page: woocommerce.is_checkout,
  };

  if (cart.items && Array.isArray(cart.items) && cart.items.length > 0) {
    cartContext.items = cart.items.map(item => ({
      product_id: item.id,
      name: item.name,
      quantity: item.quantity,
      price_display: item.price_display || item.price,
    }));
  }

  const hasValue = Object.values(cartContext).some((value) => value !== undefined && value !== null && value !== '');
  return hasValue ? cartContext : undefined;
}

export const useChatStore = create<ChatState>()(
  persist(
    (set, get) => ({
      // Initial state
      isOpen: false,
      isTyping: false,
      isStreaming: false,
      streamingContent: '',
      unreadCount: 0,
      isLoaded: false,
      isThinking: false,
      thinkingMessage: null,
      orchestratorState: 'IDLE',
      isUserTyping: false,
      inputFocused: false,
      lastUserTypedAt: 0,
      config: defaultConfig,
      currentBotId: null,
      previousBotId: null,
      previousPageUrl: null,
      lastWidgetAssignments: null,
      visitorId: generateVisitorId(),
      conversationId: null,
      messages: [],
      activeWidget: null,
      cart: null,
      isCartOpen: false,
      isCartLoading: false,
      cartError: null,
      lastBundleCouponCode: null,
      firedTriggers: new Set<string>(),
      cooldownUntil: null,
      remainingHour: null,
      remainingDay: null,
      
      // Toggle chat window
      toggleChat: () => {
        const wasOpen = get().isOpen;
        set({ 
          isOpen: !wasOpen,
          unreadCount: !wasOpen ? 0 : get().unreadCount
        });

        if (!wasOpen && isCartButtonEnabled(get())) {
          void get().refreshCart();
        }
        
        // Send welcome message on first open
        if (!wasOpen && get().messages.length === 0) {
          const config = get().config;
          const openingScreen = config.opening_screen;
          const hasOpeningScreen = openingScreen?.has_opening_screen && openingScreen?.mode;
          
          if (hasOpeningScreen) {
            // Opening screen is configured — inject welcome_text as a scripted bot message
            // if show_welcome is enabled. Content appears instantly since the opening screen
            // overlay hides the message list anyway; it becomes visible when the user interacts.
            if (openingScreen?.show_welcome !== false && openingScreen?.welcome_text) {
              const welcomeMessage: ChatMessage = {
                id: 'welcome-' + Date.now(),
                role: 'bot',
                content: openingScreen.welcome_text,
                timestamp: new Date()
              };
              set({ messages: [welcomeMessage] });
            }
          } else {
            // No opening screen — stream the welcome message with a natural typing effect
            // Respect show_welcome_message flag (defaults to true for backward compatibility)
            const showWelcome = config.show_welcome_message !== false;
            if (showWelcome) {
              const welcomeId = 'welcome-' + Date.now();
              const welcomeContent = config.welcome_message || defaultConfig.welcome_message;
              const welcomeMessage: ChatMessage = {
                id: welcomeId,
                role: 'bot',
                content: '',
                timestamp: new Date(),
                isStreaming: true,
              };
              proactiveTimer.onSSEStart();
              set({ messages: [welcomeMessage], isStreaming: true });
              simulateScriptedStream(set, welcomeId, welcomeContent, 25, { notifyTimerOnComplete: true });
            }
          }
        }
      },
      
      // Open chat
      openChat: () => {
        if (get().isOpen) return;
        get().toggleChat();
      },
      
      // Close chat
      closeChat: () => {
        if (!get().isOpen) return;
        set({ isOpen: false, isCartOpen: false });
      },

      openCart: () => {
        if (!isCartButtonEnabled(get())) return;
        const state = get();
        set({ isCartOpen: true, isOpen: true, unreadCount: 0 });
        trackCommerceEvent({
          event_type: 'cart_opened',
          conversation_id: state.conversationId,
          visitor_id: state.visitorId,
          bot_id: state.currentBotId,
          language: state.config.language,
          ...getCartTelemetryFields(state.cart),
          metadata: {
            source: 'drawer',
          },
        });
      },

      closeCart: () => {
        if (!get().isCartOpen) return;
        set({ isCartOpen: false });
      },

      toggleCart: () => {
        if (!isCartButtonEnabled(get())) return;
        const state = get();
        const nextIsCartOpen = !state.isCartOpen;
        set({ isCartOpen: nextIsCartOpen, isOpen: true, unreadCount: 0 });

        if (nextIsCartOpen) {
          trackCommerceEvent({
            event_type: 'cart_opened',
            conversation_id: state.conversationId,
            visitor_id: state.visitorId,
            bot_id: state.currentBotId,
            language: state.config.language,
            ...getCartTelemetryFields(state.cart),
            metadata: {
              source: 'drawer',
            },
          });
        }
      },

      refreshCart: async () => {
        if (!isCartButtonEnabled(get())) {
          set({ cart: null, cartError: null, isCartLoading: false, isCartOpen: false });
          return null;
        }

        set({ isCartLoading: true, cartError: null });
        try {
          const cart = await chatApi.getCart();
          set({ cart, isCartLoading: false });
          cartDebugLog('refreshCart applied', {
            count: getWooCartCount(cart),
            items: cart?.items.length ?? 0,
            is_empty: cart?.is_empty ?? true,
          });
          return cart;
        } catch (error) {
          const message = error instanceof Error ? error.message : t('Failed to load cart');
          set({ isCartLoading: false, cartError: message });
          return get().cart;
        }
      },

      setCartState: (cart: WooCart | null) => {
        set({ cart, isCartLoading: false, cartError: null });
        cartDebugLog('setCartState applied', {
          count: getWooCartCount(cart),
          items: cart?.items.length ?? 0,
          is_empty: cart?.is_empty ?? true,
        });
        const updatedCart = get().cart;
        console.info('[BoostBot][Cart] setCartState PROOF:', {
          itemCount: updatedCart?.count ?? updatedCart?.items?.length ?? 'MISSING',
          hasItems: Array.isArray(updatedCart?.items) && updatedCart.items.length > 0,
          keys: updatedCart ? Object.keys(updatedCart) : 'NULL_CART',
        });
      },

      addToCartAndSync: async ({ productId, quantity = 1, variationId = 0, variation = {} }) => {
        if (!isWooCommerceEnabled(get())) {
          const error = t('Cart is unavailable');
          set({ cartError: error });
          return { success: false, cart: get().cart, error };
        }

        set({ isCartLoading: true, cartError: null });
        try {
          const result = await chatApi.addToCart({ productId, quantity, variationId, variation });
          cartDebugLog('addToCart response', {
            count: getWooCartCount(result.cart),
            items: result.cart?.items.length ?? 0,
            is_empty: result.cart?.is_empty ?? true,
          });
          const cart = result.cart ?? (isCartButtonEnabled(get()) ? await get().refreshCart() : get().cart);
          set({ cart, isCartLoading: false, cartError: null });
          cartDebugLog('addToCart store updated', {
            count: getWooCartCount(get().cart),
            items: get().cart?.items.length ?? 0,
            is_empty: get().cart?.is_empty ?? true,
          });
          return { success: true, cart, message: result.message };
        } catch (error) {
          const message = error instanceof Error ? error.message : t('Failed to add item to cart');
          set({ isCartLoading: false, cartError: message });
          return { success: false, cart: get().cart, error: message };
        }
      },

      updateCartItemAndSync: async (itemKey: string, quantity: number) => {
        if (!isWooCommerceEnabled(get())) {
          const error = t('Cart is unavailable');
          set({ cartError: error });
          return { success: false, cart: get().cart, error };
        }

        set({ isCartLoading: true, cartError: null });
        try {
          const result = await chatApi.updateCartItem(itemKey, quantity);
          const cart = result.cart ?? (isCartButtonEnabled(get()) ? await get().refreshCart() : get().cart);
          set({ cart, isCartLoading: false, cartError: null });
          return { success: true, cart, message: result.message };
        } catch (error) {
          const message = error instanceof Error ? error.message : t('Failed to update cart');
          set({ isCartLoading: false, cartError: message });
          return { success: false, cart: get().cart, error: message };
        }
      },

      removeCartItemAndSync: async (itemKey: string) => {
        if (!isWooCommerceEnabled(get())) {
          const error = t('Cart is unavailable');
          set({ cartError: error });
          return { success: false, cart: get().cart, error };
        }

        set({ isCartLoading: true, cartError: null });
        try {
          const result = await chatApi.removeCartItem(itemKey);
          const cart = result.cart ?? (isCartButtonEnabled(get()) ? await get().refreshCart() : get().cart);
          set({ cart, isCartLoading: false, cartError: null });
          return { success: true, cart, message: result.message };
        } catch (error) {
          const message = error instanceof Error ? error.message : t('Failed to remove item from cart');
          set({ isCartLoading: false, cartError: message });
          return { success: false, cart: get().cart, error: message };
        }
      },

      applyCouponAndSync: async (code: string) => {
        if (!isWooCommerceEnabled(get())) {
          const error = t('Cart is unavailable');
          set({ cartError: error });
          return { success: false, cart: get().cart, error };
        }

        set({ isCartLoading: true, cartError: null });
        try {
          const result = await chatApi.applyCoupon(code);
          const cart = result.cart ?? (isCartButtonEnabled(get()) ? await get().refreshCart() : get().cart);
          set({ cart, isCartLoading: false, cartError: null, lastBundleCouponCode: null });
          return { success: true, cart, message: result.message, discountDisplay: result.discountDisplay };
        } catch (error) {
          const message = error instanceof Error ? error.message : t('Failed to apply coupon');
          set({ isCartLoading: false, cartError: message });
          return { success: false, cart: get().cart, error: message };
        }
      },

      removeCouponAndSync: async (code: string) => {
        if (!isWooCommerceEnabled(get())) {
          const error = t('Cart is unavailable');
          set({ cartError: error });
          return { success: false, cart: get().cart, error };
        }

        set({ isCartLoading: true, cartError: null });
        try {
          const result = await chatApi.removeCoupon(code);
          const cart = result.cart ?? (isCartButtonEnabled(get()) ? await get().refreshCart() : get().cart);
          set((state) => ({
            cart,
            isCartLoading: false,
            cartError: null,
            lastBundleCouponCode: state.lastBundleCouponCode === code ? null : state.lastBundleCouponCode,
          }));
          return { success: true, cart, message: result.message };
        } catch (error) {
          const message = error instanceof Error ? error.message : t('Failed to remove coupon');
          set({ isCartLoading: false, cartError: message });
          return { success: false, cart: get().cart, error: message };
        }
      },

      setLastBundleCouponCode: (code: string | null) => {
        set({ lastBundleCouponCode: code });
      },
      
      // Open chat with proactive message (for triggers)
      openWithMessage: (message: string, triggerId?: string) => {
        // Don't show if already open
        if (get().isOpen) {
          return;
        }
        
        // Check if this trigger already fired (if triggerId provided)
        if (triggerId && get().firedTriggers.has(triggerId)) {
          return;
        }
        
        // Create proactive message from bot
        const proactiveMessage: ChatMessage = {
          id: `proactive-${Date.now()}`,
          role: 'bot',
          content: message,
          timestamp: new Date()
        };
        
        // If first message, add welcome message first, then proactive
        const currentMessages = get().messages;
        const newMessages = currentMessages.length === 0 
          ? [proactiveMessage]
          : [...currentMessages, proactiveMessage];
        
        set({ 
          isOpen: true,
          messages: newMessages,
          unreadCount: 0
        });
        
        // Mark trigger as fired
        if (triggerId) {
          get().markTriggerFired(triggerId);
        }
      },
      
      // Mark trigger as fired
      markTriggerFired: (triggerId: string) => {
        const triggers = new Set(get().firedTriggers);
        triggers.add(triggerId);
        set({ firedTriggers: triggers });
        
        // Also save to localStorage to persist across page loads (language-scoped)
        const key = getStorageKey(`boostbot_trigger_${triggerId}`);
        localStorage.setItem(key, Date.now().toString());
      },
      
      // Check if trigger already fired (including from localStorage)
      hasTriggerFired: (triggerId: string) => {
        // Check memory first
        if (get().firedTriggers.has(triggerId)) {
          return true;
        }
        
        // Check localStorage (persist for 24 hours, language-scoped)
        const key = getStorageKey(`boostbot_trigger_${triggerId}`);
        const stored = localStorage.getItem(key);
        if (stored) {
          const timestamp = parseInt(stored, 10);
          const dayInMs = 24 * 60 * 60 * 1000;
          if (Date.now() - timestamp < dayInMs) {
            return true;
          }
          // Expired, remove it
          localStorage.removeItem(key);
        }
        
        return false;
      },
      
      // Update streaming content (for progressive display during SSE streaming)
      updateStreamingContent: (content: string) => {
        set({ streamingContent: content });
      },

      // Set thinking indicator (Gemini is analyzing)
      setThinking: (message: string | null) => {
        set({ 
          isThinking: !!message,
          thinkingMessage: message 
        });
      },

      // Orchestrator actions (Phase 2)
      userStartedTyping: () => {
        set({ isUserTyping: true, lastUserTypedAt: Date.now() });
        orchestrator.userStartedTyping();
      },

      userStoppedTyping: () => {
        set({ isUserTyping: false });
        orchestrator.userStoppedTyping();
      },

      setInputFocused: (focused: boolean) => {
        set({ inputFocused: focused });
      },

      initOrchestrator: () => {
        console.log('%c[BoostBot][Orchestrator] === INITIALIZING STATE MACHINE ===', 'color: #10B981; font-weight: bold');
        
        // Set up orchestrator callbacks
        orchestrator.onState((state) => {
          console.log(`[BoostBot][Orchestrator] State changed to: ${state}`);
          set({ orchestratorState: state });
        });

        orchestrator.onThinkingChange((message) => {
          set({ 
            isThinking: !!message,
            thinkingMessage: message 
          });
        });

        orchestrator.onProactive((message, widget) => {
          console.log('[BoostBot][Store] Proactive message received', { message, widget });
          
          // Add proactive bot message
          const botMessage: ChatMessage = {
            id: `bot-proactive-${Date.now()}`,
            role: 'bot',
            content: message,
            timestamp: new Date(),
            widgets: widget ? [{ type: widget.type, id: 'proactive', config: {} }] : [],
          };
          
          set(state => ({
            messages: [...state.messages, botMessage],
            activeWidget: widget ? { type: widget.type, id: 'proactive', config: {} } : state.activeWidget,
          }));

          // Increment unread if chat is closed
          if (!get().isOpen) {
            set(state => ({ unreadCount: state.unreadCount + 1 }));
          }
        });
      },
      
      // Send message (with optional SSE streaming for lower perceived latency)
      sendMessage: async (content: string, options?: SendMessageOptions) => {
        // ── Per-visitor cooldown guard (Week 33) ────────────────────
        const { cooldownUntil } = get();
        if (cooldownUntil && Date.now() < cooldownUntil) {
          console.warn('[BoostBot] Message blocked by cooldown timer');
          return;
        }

        const { visitorId, conversationId, messages, config, currentBotId, cart } = get();
        proactiveTimer.onMessageSent();
        
        const userMessage: ChatMessage = {
          id: `user-${Date.now()}`,
          role: 'user',
          content,
          timestamp: new Date()
        };
        
        set({ 
          messages: [...messages, userMessage],
          isTyping: true,
          isStreaming: false,
          streamingContent: ''
        });
        
        try {
          // Get page context - IMPORTANT: include post_id for bot resolution!
          const pageConfig = window.boostbot_config;
          const context = {
            id: pageConfig?.current_page?.id || 0,
            post_id: pageConfig?.current_page?.id || 0,
            page_id: pageConfig?.current_page?.id || 0,
            page_url: pageConfig?.current_page?.url || window.location.href,
            page_title: pageConfig?.current_page?.title || document.title,
            url: pageConfig?.current_page?.url || window.location.href,
            referrer: document.referrer
          };
          
          // Build history from existing messages (for context in follow-up queries)
          // Include widget data so the retrieval endpoint can track shown widgets
          const existingHistory = messages
            .filter(m => m.content && m.role)
            .map(m => ({
              role: m.role === 'bot' ? 'assistant' : 'user',
              content: m.content,
              ...(m.widgets?.length ? { widgets: m.widgets.map(w => ({ type: w.type, id: w.id })) } : {}),
            }));
          const cartContext = buildCartContextLite(cart, get());
          
          const request: import('@/types').SendMessageRequest = {
            message: content,
            visitor_id: visitorId,
            conversation_id: conversationId,
            protocol_version: '2.0',
            context,
            ...(cartContext ? { cart_context: cartContext } : {}),
            history: existingHistory, // Full conversation history for Gemini context
            bot_id: currentBotId,  // Include bot_id for widget detection
            // Pass per-question AI instructions as a dedicated field (injected into
            // system prompt on the backend with high priority)
            ...(options?.aiInstructions ? { ai_instructions: options.aiInstructions } : {}),
          };
          
          // Canonical unified chat flow:
          const backendStreamUrl = config.backend_stream_url || config.vercel_stream_url;
          const backendStreamConfigured = !!backendStreamUrl;

          if (backendStreamConfigured) {
            // ORCHESTRATED MODE: Gemini orchestrates GPT with progressive responses
            // Flow: WordPress (two-tier chunks) → Gemini (decides) → GPT (quick) → GPT (detailed)
            const botMessageId = `bot-${Date.now()}`;
            
            const placeholderMessage: ChatMessage = {
              id: botMessageId,
              role: 'bot',
              content: '',
              timestamp: new Date(),
              isStreaming: true
            };
            
            set(state => ({
              messages: [...state.messages, placeholderMessage],
              isTyping: false,
              isStreaming: true,
              streamingContent: ''
            }));
            proactiveTimer.onSSEStart();
            
            await chatApi.sendMessageOrchestrated(
              request,
              // On each chunk
              (_chunk: string, accumulated: string) => {
                // Debug: Log store updates with more detail
                try {
                  const logs = JSON.parse(sessionStorage.getItem('boostbot_store_debug') || '[]');
                  const currentMessages = get().messages;
                  const targetMsg = currentMessages.find(m => m.id === botMessageId);
                  logs.push({ 
                    t: Date.now(), 
                    chars: accumulated.length, 
                    msgId: botMessageId,
                    found: !!targetMsg,
                    msgCount: currentMessages.length
                  });
                  if (logs.length > 50) logs.shift();
                  sessionStorage.setItem('boostbot_store_debug', JSON.stringify(logs));
                } catch { /* ignore */ }
                
                set(state => {
                  const updatedMessages = state.messages.map(m => 
                    m.id === botMessageId 
                      ? { ...m, content: accumulated, isStreaming: true } 
                      : m
                  );
                  return { 
                    messages: updatedMessages,
                    streamingContent: accumulated 
                  };
                });
              },
              // On complete
              (response) => {
                const currentState = get();
                
                set(state => {
                  const updatedMessages = state.messages.map(m => 
                    m.id === botMessageId 
                      ? { 
                          ...m, 
                          content: response.data.reply,
                          widgets: response.data.widgets || [],
                          isStreaming: false 
                        } 
                      : m
                  );
                  return { 
                    messages: updatedMessages,
                    conversationId: response.data.conversation_id,
                    isStreaming: false,
                    isThinking: false,
                    thinkingMessage: null,
                    streamingContent: '',
                    activeWidget: response.data.widgets?.[0] || null
                  };
                });
                
                if (!currentState.isOpen) {
                  set(state => ({ unreadCount: state.unreadCount + 1 }));
                }
                
                // === PROACTIVE TIMER MANAGER SETUP ===
                // Get behavior from the response's bot_config (returned from retrieval endpoint)
                const botConfig = response.data.bot_config;
                const behavior = botConfig?.behavior || {};
                
                // Persist widget assignments for proactive/page-transition guard
                if (botConfig?.widget_assignments) {
                  set({ lastWidgetAssignments: botConfig.widget_assignments });
                }
                
                // Configure proactive timer from bot settings
                const proactiveEnabled = behavior.proactive_engagement === true;
                const proactiveDelaySeconds = Number(behavior.proactive_delay_seconds) || 10;
                
                proactiveTimer.setEnabled(proactiveEnabled);
                proactiveTimer.setDelay(proactiveDelaySeconds * 1000);
                
                // Set up context getter (always returns FRESH context)
                proactiveTimer.setContextGetter(() => {
                  const state = get();
                  if (!state.conversationId) return null;
                  
                  const history = state.messages
                    .filter(m => m.content && m.role)
                    .map(m => ({
                      role: m.role === 'bot' ? 'assistant' : 'user',
                      content: m.content,
                      ...(m.widgets?.length ? { widgets: m.widgets.map(w => ({ type: w.type, id: w.id })) } : {}),
                    }));
                  
                  const lastBotMsg = [...state.messages].reverse().find(m => m.role === 'bot');
                  const lastAnyMsg = state.messages.length > 0 ? state.messages[state.messages.length - 1] : null;
                  const lastMessageAtMs = lastAnyMsg ? new Date(lastAnyMsg.timestamp).getTime() : undefined;
                  
                  return {
                    conversationId: state.conversationId,
                    visitorId: state.visitorId,
                    history,
                    lastBotResponse: lastBotMsg?.content || '',
                    lastMessageAtMs,
                    shownWidgetIds: state.messages
                      .flatMap(m => m.widgets || [])
                      .map(w => typeof w.id === 'number' ? w.id : parseInt(String(w.id), 10))
                      .filter(id => !isNaN(id)),
                    botConfig: {
                      name: botConfig?.name || config.bot_name,
                      personality: botConfig?.personality || '',
                      output_limit: botConfig?.output_limit || config.bot_output_char_limit || 800,
                      enabled_widgets: botConfig?.enabled_widgets || config.widgets_enabled || [],
                      language: botConfig?.language || 'he',
                      widget_assignments: botConfig?.widget_assignments,
                    },
                  };
                });
                
                // Set up message count getter (for stale response detection)
                proactiveTimer.setMessageCountGetter(() => get().messages.length);
                proactiveTimer.setUserMessageCountGetter(() => get().messages.filter(m => m.role === 'user').length);
                
                // Set up active widget getter — checks if the last message
                // in the conversation has a widget displayed. When true, the
                // proactive timer stays frozen to avoid interrupting the user.
                proactiveTimer.setHasActiveWidgetGetter(() => {
                  const state = get();
                  // Check 1: explicit activeWidget set by the chat system
                  if (state.activeWidget) return true;
                  // Check 2: last bot message has widgets attached
                  const msgs = state.messages;
                  for (let i = msgs.length - 1; i >= 0; i--) {
                    const m = msgs[i];
                    if (m.role === 'bot') {
                      return !!(m.widgets && m.widgets.length > 0);
                    }
                    if (m.role === 'user') break; // user message after bot = no widget blocking
                  }
                  return false;
                });
                
                // Set up proactive engage callback — streaming is handled internally
                // by proactiveEngage() via /chat/respond (no separate stream callback needed).
                proactiveTimer.setProactiveEngageCallback(async (context) => {
                  const actualIdleMs = context.lastMessageAtMs && Number.isFinite(context.lastMessageAtMs)
                    ? Math.max(0, Date.now() - context.lastMessageAtMs)
                    : proactiveDelaySeconds * 1000;

                  const proactiveMsgId = `proactive-${Date.now()}`;
                  let streamStarted = false;
                  const abortController = proactiveTimer.getAbortController();

                  return await chatApi.proactiveEngage({
                    visitorId: context.visitorId,
                    conversationId: context.conversationId,
                    lastBotResponse: context.lastBotResponse,
                    history: context.history,
                    shownWidgetIds: context.shownWidgetIds,
                    timeSinceLastMessage: actualIdleMs,
                    wasUserTyping: false,
                    botConfig: context.botConfig,
                    abortSignal: abortController.signal,
                    onStreamStart: () => {
                      streamStarted = true;
                      const proactiveMessage: ChatMessage = {
                        id: proactiveMsgId,
                        role: 'bot',
                        content: '',
                        timestamp: new Date(),
                        isStreaming: true,
                      };
                      set(state => ({
                        messages: [...state.messages, proactiveMessage],
                        isStreaming: true,
                      }));
                    },
                    onChunk: (_chunk, accumulated) => {
                      set(state => ({
                        messages: state.messages.map(m =>
                          m.id === proactiveMsgId ? { ...m, content: accumulated, isStreaming: true } : m
                        ),
                      }));
                    },
                    onStreamComplete: (content) => {
                      proactiveTimer.clearAbortController();
                      set(state => ({
                        messages: state.messages.map(m =>
                          m.id === proactiveMsgId ? { ...m, content, isStreaming: false } : m
                        ),
                        isStreaming: false,
                      }));
                    },
                    onStreamError: (error) => {
                      proactiveTimer.clearAbortController();
                      console.error('[BoostBot][Proactive] Stream error:', error);
                      if (streamStarted) {
                        set(state => ({
                          messages: state.messages.filter(m => m.id !== proactiveMsgId),
                          isStreaming: false,
                        }));
                      }
                    },
                  });
                });
                
                // Set up widget callback
                // IMPORTANT: widgets are rendered from message.widgets in Message.tsx.
                // Updating only activeWidget is not enough for visual rendering.
                proactiveTimer.setShowWidgetCallback((widget) => {
                  const normalizedWidget: Widget = {
                    type: widget.type,
                    id: widget.id,
                    config: widget.config || {},
                  };

                  set(state => {
                    const messages = [...state.messages];
                    let attachedToExistingBotMessage = false;

                    // Prefer attaching to the latest bot message (natural UX: "here's the form" + form below it).
                    for (let i = messages.length - 1; i >= 0; i--) {
                      const msg = messages[i];
                      if (msg.role !== 'bot') continue;

                      const existingWidgets = msg.widgets || [];
                      const alreadyAttached = existingWidgets.some(w =>
                        String(w.id) === String(normalizedWidget.id) && w.type === normalizedWidget.type
                      );

                      if (!alreadyAttached) {
                        messages[i] = {
                          ...msg,
                          widgets: [...existingWidgets, normalizedWidget],
                        };
                      }

                      attachedToExistingBotMessage = true;
                      break;
                    }

                    // Fallback: if no bot message exists, add a minimal widget-only bot message.
                    if (!attachedToExistingBotMessage) {
                      messages.push({
                        id: `bot-widget-${Date.now()}`,
                        role: 'bot',
                        content: '',
                        timestamp: new Date(),
                        widgets: [normalizedWidget],
                      });
                    }

                    return {
                      messages,
                      activeWidget: normalizedWidget,
                    };
                  });
                });
                
                // SSE complete - notify timer (this will try to start countdown if conditions met)
                proactiveTimer.onSSEComplete();
                
                // Legacy ProactiveEngine removed — see boostbot/_archive/proactive-engine/
              },
              // On error
              (error) => {
                console.error('Orchestrated chat error:', error);
                proactiveTimer.onSSEComplete();
                set(state => {
                  const updatedMessages = state.messages.map(m => 
                    m.id === botMessageId 
                      ? { ...m, content: t('Sorry, an error occurred. Please try again later.') + ' 😔', isStreaming: false } 
                      : m
                  );
                  return { 
                    messages: updatedMessages,
                    isStreaming: false,
                    isThinking: false,
                    thinkingMessage: null,
                    streamingContent: ''
                  };
                });
              },
              // On thinking
              (thinkingMsg) => {
                set({ 
                  isThinking: !!thinkingMsg,
                  thinkingMessage: thinkingMsg 
                });
              }
              // Note: Proactive engagement is now handled by ProactiveTimerManager (centralized)
              // The timer manager is configured in the onComplete callback above
            );
          } else {
            throw new Error('Orchestrated chat flow is required but backend stream URL is unavailable.');
          }
          
        } catch (error) {
          console.error('Chat error:', error);
          proactiveTimer.onSSEComplete();
          
          // ── Rate-limit error handling (Week 33) ────────────────
          if (error instanceof RateLimitError) {
            // Apply cooldown
            if (error.retryAfter > 0) {
              set({ cooldownUntil: Date.now() + error.retryAfter * 1000 });
            } else if (error.cooldownSeconds > 0) {
              set({ cooldownUntil: Date.now() + error.cooldownSeconds * 1000 });
            }
            set({
              remainingHour: error.remainingHour,
              remainingDay: error.remainingDay,
            });

            // Show the rate-limit reply as a bot message
            const rateLimitMessage: ChatMessage = {
              id: `ratelimit-${Date.now()}`,
              role: 'bot',
              content: error.reply,
              timestamp: new Date()
            };
            set(state => ({
              // Drop any empty streaming placeholder bot message left behind by the
              // orchestrated flow before appending the rate-limit reply.
              messages: [
                ...state.messages.filter(m => !(m.role === 'bot' && m.isStreaming && !m.content)),
                rateLimitMessage,
              ],
              isTyping: false,
              isStreaming: false,
              streamingContent: ''
            }));
            return;
          }

          // Error message
          const errorMessage: ChatMessage = {
            id: `error-${Date.now()}`,
            role: 'bot',
            content: t('Sorry, an error occurred. Please try again later.') + ' 😔',
            timestamp: new Date()
          };
          
          set(state => ({
            messages: [...state.messages, errorMessage],
            isTyping: false,
            isStreaming: false,
            streamingContent: ''
          }));
        }
      },
      
      // Load config from API
      loadConfig: async () => {
        try {
          console.log('[BoostBot][Debug] Loading config...');
          const response = await chatApi.getConfig();
          console.log('[BoostBot][Debug] Config response:', response);
          
          if (response.success && response.data) {
            // eslint-disable-next-line @typescript-eslint/no-explicit-any
            const rd = response.data as any;
            console.log('[BoostBot][Debug] Config data:', {
              direct_streaming_enabled: rd.direct_streaming_enabled,
              backend_stream_url: rd.backend_stream_url,
              vercel_stream_url: rd.vercel_stream_url,
              streaming_enabled: rd.streaming_enabled,
            });
            const currentState = get();
            const currentBotId = currentState.currentBotId;
            const newBotId = response.data.bot_id;
            const pendingSearchTransition = readPendingSearchTransitionContext();
            const previousPageUrl = pendingSearchTransition?.sourceUrl || currentState.previousPageUrl;
            const currentPageUrl = window.location.href;
            
            // Detect bot change (page transition to different bot)
            const botChanged = currentBotId && newBotId && currentBotId !== newBotId;
            const searchRedirectTriggered = !!pendingSearchTransition?.query;
            
            // Update state - PRESERVE conversation even when bot changes
            set({ 
              config: { ...defaultConfig, ...response.data },
              currentBotId: newBotId,
              previousBotId: botChanged ? currentBotId : currentState.previousBotId,
              previousPageUrl: currentPageUrl,
              isLoaded: true 
            });

            if (typeof window !== 'undefined' && window.boostbot_config) {
              window.boostbot_config.bot_id = newBotId ?? undefined;
            }
            
            // Handle bot change or search-caused redirect transition
            if (botChanged || searchRedirectTriggered) {
              // Cancel any pending idle proactive triggers (but allow transition trigger)
              proactiveTimer.onPageNavigation();
              
              // Fire page transition trigger if:
              // 1. It is enabled globally
              // 2. We either have conversation history or a pending search redirect context
              const pageTransitionEnabled = response.data.page_transition_triggers_enabled !== false;
              const canTrigger = currentState.messages.length > 0 || searchRedirectTriggered;
              if (canTrigger && pageTransitionEnabled && newBotId) {
                const triggerReason = searchRedirectTriggered ? 'search_redirect' : 'bot_change';
                const previousBotId = currentBotId ?? newBotId;
                console.log('[BoostBot] Firing page transition trigger', {
                  triggerReason,
                  previousBotId,
                  newBotId,
                  previousPageUrl: previousPageUrl || currentPageUrl,
                  currentPageUrl,
                  searchQuery: pendingSearchTransition?.query || null,
                });
                // Use setTimeout to ensure state is updated first
                setTimeout(() => {
                  get().firePageTransitionTrigger({
                    previousBotId,
                    newBotId,
                    previousPageUrl: previousPageUrl || currentPageUrl,
                    triggerReason,
                    searchQuery: pendingSearchTransition?.query,
                  });
                }, 100);
              } else if (!pageTransitionEnabled) {
                console.log('[BoostBot] Page transition triggers disabled globally, skipping');
              }

              if (searchRedirectTriggered) {
                clearPendingSearchTransitionContext();
              }
            }
            
            // Initialize orchestrator if orchestrated flow is enabled
            if (response.data.orchestrated_flow_enabled) {
              console.log('[BoostBot][Debug] Orchestrated flow enabled, initializing orchestrator');
              get().initOrchestrator();
            }

            if (isCartButtonEnabled(get())) {
              void get().refreshCart();
            } else {
              set({ cart: null, cartError: null, isCartLoading: false, isCartOpen: false });
            }
          }
        } catch (error) {
          console.error('Failed to load config:', error);
          set({ isLoaded: true }); // Still mark as loaded with defaults
        }
      },
      
      // Widget management
      setActiveWidget: (widget) => set({ activeWidget: widget }),
      
      // Add a message (for external use)
      addMessage: (message) => set(state => ({
        messages: [...state.messages, message],
        unreadCount: !state.isOpen ? state.unreadCount + 1 : state.unreadCount
      })),
      
      // Add a scripted Q&A exchange without calling the AI (for questions_answers mode)
      addScriptedExchange: (question: string, answer: string) => {
        proactiveTimer.onMessageSent();
        proactiveTimer.onSSEStart();
        const userMessage: ChatMessage = {
          id: `user-${Date.now()}`,
          role: 'user',
          content: question,
          timestamp: new Date()
        };
        const botMessageId = `bot-scripted-${Date.now()}`;
        const botMessage: ChatMessage = {
          id: botMessageId,
          role: 'bot',
          content: '',
          timestamp: new Date(),
          isStreaming: true,
        };
        // Add user message + empty bot message placeholder with streaming flag
        set(state => ({
          messages: [...state.messages, userMessage, botMessage],
          isStreaming: true,
        }));
        // Stream the answer word-by-word for a natural typing effect
        simulateScriptedStream(set, botMessageId, answer, 30, { notifyTimerOnComplete: true });
      },
      
      // Clear chat
      clearChat: () => {
        // Cancel any pending proactive triggers
        proactiveTimer.onChatReset();
        
        set({
          messages: [],
          conversationId: null,
          activeWidget: null,
          lastBundleCouponCode: null,
        });
      },
      
      // Fire page transition trigger when navigation suggests contextual follow-up.
      firePageTransitionTrigger: async ({ previousBotId, newBotId, previousPageUrl, triggerReason = 'bot_change', searchQuery }: {
        previousBotId: number;
        newBotId: number;
        previousPageUrl: string;
        triggerReason?: 'bot_change' | 'search_redirect';
        searchQuery?: string;
      }) => {
        const state = get();
        const hasSearchRedirectIntent = triggerReason === 'search_redirect' && !!searchQuery?.trim();
        
        // Need orchestrated flow; conversation history is optional for search redirects.
        if (!state.config.orchestrated_flow_enabled) {
          console.log('[BoostBot] Page transition: Orchestrated flow disabled');
          return;
        }

        if (!state.conversationId && !hasSearchRedirectIntent) {
          console.log('[BoostBot] Page transition: No conversation context available');
          return;
        }
        
        // Capture generation to detect if another navigation happens during the call
        const generationAtTrigger = proactiveTimer.getGeneration();
        
        // Build context for the page transition
        const history = state.messages
          .filter(m => m.content && m.role)
          .map(m => ({
            role: m.role === 'bot' ? 'assistant' : 'user',
            content: m.content
          }));
        
        const lastBotMsg = [...state.messages].reverse().find(m => m.role === 'bot');
        
        console.log('[BoostBot] Firing page transition trigger', {
          previousBotId,
          newBotId,
          previousPageUrl,
          currentPageUrl: window.location.href,
          messageCount: history.length,
          generation: generationAtTrigger,
          triggerReason,
          searchQuery: searchQuery || null,
        });
        
        // Show thinking indicator while orchestrator decides
        set({ isThinking: true, thinkingMessage: '🔄 ...' });
        
        try {
          const transitionMsgId = `transition-${Date.now()}`;
          let streamStarted = false;

          const result = await chatApi.pageTransitionEngage({
            visitorId: state.visitorId,
            conversationId: state.conversationId,
            history,
            lastBotResponse: lastBotMsg?.content || '',
            shownWidgetIds: state.messages
              .flatMap(m => m.widgets || [])
              .map(w => typeof w.id === 'number' ? w.id : parseInt(String(w.id), 10))
              .filter(id => !isNaN(id)),
            pageTransitionContext: {
              previousBotId,
              newBotId,
              previousPageUrl,
              currentPageUrl: window.location.href,
              triggerReason,
              searchQuery: searchQuery?.trim() || undefined,
            },
            botConfig: {
              name: state.config.bot_name,
              personality: '',
              output_limit: state.config.bot_output_char_limit || 800,
              enabled_widgets: (state.config.widgets_enabled || []).map(w => typeof w === 'number' ? w : parseInt(String(w), 10)).filter(n => !isNaN(n)),
              language: getCurrentLocale(),
              widget_assignments: state.lastWidgetAssignments || undefined,
            },
            onMetaReceived: () => {
              set({ isThinking: false, thinkingMessage: null });
            },
            onStreamStart: () => {
              streamStarted = true;
              proactiveTimer.onSSEStart();
              const transitionMessage: ChatMessage = {
                id: transitionMsgId,
                role: 'bot',
                content: '',
                timestamp: new Date(),
                isStreaming: true,
              };
              set(s => ({
                messages: [...s.messages, transitionMessage],
                isStreaming: true,
              }));
            },
            onChunk: (_chunk: string, accumulated: string) => {
              set(s => ({
                messages: s.messages.map(m =>
                  m.id === transitionMsgId
                    ? { ...m, content: accumulated, isStreaming: true }
                    : m
                ),
              }));
            },
            onStreamComplete: () => {
              console.log('[BoostBot] Page transition message complete');
              proactiveTimer.onSSEComplete();
              set(s => ({
                messages: s.messages.map(m =>
                  m.id === transitionMsgId
                    ? { ...m, isStreaming: false }
                    : m
                ),
                isStreaming: false,
              }));
            },
            onStreamError: (error: Error) => {
              console.error('[BoostBot] Page transition stream error:', error);
              proactiveTimer.onSSEComplete();
              set({ isStreaming: false });
            },
          });

          if (!streamStarted) {
            set({ isThinking: false, thinkingMessage: null });
          }

          if (proactiveTimer.getGeneration() !== generationAtTrigger) {
            console.log('[BoostBot] Page transition: Generation changed, discarding stale response');
            return;
          }

          if (!result) {
            console.log('[BoostBot] Page transition: Gemini decided to WAIT');
            return;
          }

          if (result.action === 'SHOW_WIDGET' && result.widget) {
            const normalizedWidget = {
              type: result.widget.type,
              id: result.widget.id,
              config: result.widget.config || {},
            };
            set(state2 => {
              const messages = [...state2.messages];
              for (let i = messages.length - 1; i >= 0; i--) {
                if (messages[i].role === 'bot') {
                  const existing = messages[i].widgets || [];
                  if (!existing.some(w => String(w.id) === String(normalizedWidget.id) && w.type === normalizedWidget.type)) {
                    messages[i] = { ...messages[i], widgets: [...existing, normalizedWidget] };
                  }
                  break;
                }
              }
              return { messages, activeWidget: normalizedWidget };
            });
          }
        } catch (error) {
          console.error('[BoostBot] Page transition trigger failed:', error);
          set({ isThinking: false, thinkingMessage: null, isStreaming: false });
        }
      }
    }),
    {
      name: 'boostbot-chat',
      storage: localeScopedStorage,
      partialize: (state) => ({
        visitorId: state.visitorId,
        conversationId: state.conversationId,
        currentBotId: state.currentBotId,
        previousBotId: state.previousBotId,
        previousPageUrl: state.previousPageUrl,
        lastWidgetAssignments: state.lastWidgetAssignments,
        messages: state.messages
          .filter(m => m.role !== 'bot' || (m.content && m.content.trim().length > 0))
          .slice(-30)
          .map(m => ({
            ...m,
            content: typeof m.content === 'string' ? m.content : String(m.content ?? ''),
            timestamp: m.timestamp instanceof Date ? m.timestamp.toISOString() : m.timestamp
          }))
      }),
      onRehydrateStorage: () => (state) => {
        if (state?.messages) {
          state.messages = state.messages
            .filter(m => m.role !== 'bot' || (m.content && m.content.trim().length > 0))
            .map(m => {
              let content = typeof m.content === 'string' ? m.content : String(m.content ?? '');
              if (content.startsWith('undefined') && content.length > 9) {
                content = content.slice(9);
              }
              return {
                ...m,
                content,
                timestamp: new Date(m.timestamp),
              };
            });
        }
      }
    }
  )
);
