/**
 * ═══════════════════════════════════════════════════════════════════
 * Ask Sylvan Page Component (SPA)
 * ═══════════════════════════════════════════════════════════════════
 * 
 * Extracted from pages/ask-sylvan/index.html
 * 
 * Removed:
 * - TopBar (standalone: none — immersive view; Explore TV: chrome inside Explore only)
 * - Global Bottom Tab Bar (app shell hidden on `ask-sylvan`; standalone uses **InternalShellTwinNav**)
 * - Side Drawer (handled by app.html)
 * - TabButton component (shared in app.html)
 * - DrawerItem component (shared in app.html)
 * - Authentication checks (handled by router)
 * 
 * Uses:
 * - window.SylvanFlowState for profile state
 * - window.SylvanFlowRouter for navigation
 * - window.SylvanFlowAuth for authenticated API calls
 */

(function() {
  'use strict';

  const { useState, useEffect, useRef } = React;
  // ✅ GATEWAY MIGRATION: Use SF_API_BASE (Gateway Worker) instead of same-origin /api/*
  // ✅ GATEWAY MIGRATION: Use SF_API_BASE from shared-api-config.js (no duplicate declaration)
const SF_API_BASE = window.SF_API_BASE;

  // ✅ BUG 1: Assistant avatar logo fallback component
  function AssistantAvatar({ nuAskEmbedded = false }) {
    const [logoOk, setLogoOk] = useState(true);
    
    if (logoOk) {
      return (
        <div
          className={
            nuAskEmbedded
              ? 'flex h-8 w-8 shrink-0 items-center justify-center overflow-hidden rounded-full border border-[#2DE2C5]/35 bg-black/50 sm:h-10 sm:w-10'
              : 'flex h-8 w-8 shrink-0 items-center justify-center overflow-hidden rounded-full bg-white sm:h-10 sm:w-10'
          }
          style={
            nuAskEmbedded
              ? { boxShadow: '0 0 18px -4px rgba(45,226,197,0.45)' }
              : { border: '1px solid #E2E8F0' }
          }
        >
          <img
            src="/assets/sylvanflow-logo.png"
            alt="SylvanFlow"
            className="w-full h-full object-contain p-1"
            onError={() => {
              if (window.Debug) window.Debug.log('[BUG1] Assistant logo failed to load, using SF fallback');
              setLogoOk(false);
            }}
            onLoad={() => {
              if (window.Debug) window.Debug.log('[BUG1] Assistant logo loaded successfully');
            }}
          />
        </div>
      );
    } else {
      // Fallback to SF text div
      return (
        <div
          className={
            nuAskEmbedded
              ? 'flex h-8 w-8 shrink-0 items-center justify-center overflow-hidden rounded-full border border-[#2DE2C5]/40 bg-teal-950 sm:h-10 sm:w-10'
              : 'flex h-8 w-8 shrink-0 items-center justify-center overflow-hidden rounded-full sm:h-10 sm:w-10'
          }
          style={
            nuAskEmbedded
              ? { borderWidth: '1px', fontWeight: 700, fontSize: '12px', color: '#a7f3d0' }
              : { border: '1px solid #E2E8F0', background: '#0F766E', color: '#fff', fontWeight: 700, fontSize: '12px' }
          }
        >
          SF
        </div>
      );
    }
  }

  function AskSylvanPage({ embeddedExploreTv = false, embeddedPowerUpTv = false } = {}) {
    const [messages, setMessages] = useState([]);
    const [inputText, setInputText] = useState("");
    const [loading, setLoading] = useState(true);
    const [sending, setSending] = useState(false);
    // ✅ PART 2: NEVER construct chat_id using email - backend derives userKey from Bearer token
    // chat_id is returned by backend, not constructed by frontend
    const [chatId, setChatId] = useState(() => {
      try {
        const stored = localStorage.getItem('sylvanflow_chat_id');
        if (stored && typeof stored === 'string' && stored.startsWith('chat:')) {
          // ✅ PART 2: Store chat_id as-is (backend-assigned, opaque userKey)
          return stored;
        }
      } catch (e) {
        if (window.uiLogger) {
          window.uiLogger.uiWarn('[ASK-SYLVAN] Failed to load chat_id from localStorage:', e);
        }
      }
      return null;
    });
    
    // ✅ PART 2: Persist chat_id to localStorage when it changes (backend-assigned)
    useEffect(() => {
      try {
        if (chatId) {
          localStorage.setItem('sylvanflow_chat_id', chatId);
        } else {
          localStorage.removeItem('sylvanflow_chat_id');
        }
      } catch (e) {
        if (window.uiLogger) {
          window.uiLogger.uiWarn('[ASK-SYLVAN] Failed to save chat_id to localStorage:', e);
        }
      }
    }, [chatId]);

    const [typing, setTyping] = useState(false);
    const [currentLocation, setCurrentLocation] = useState(null);
    const [lastMessageTime, setLastMessageTime] = useState(null);
    const [syncLocation, setSyncLocation] = useState(true); // ✅ Default: sync location (matches current behavior)
    const [showLocationPrompt, setShowLocationPrompt] = useState(false); // Location prompt UI state
    const messagesEndRef = useRef(null);
    const chatContainerRef = useRef(null);
    const inputRef = useRef(null);
    const loadingHistoryRef = useRef(false);
    const scrollPositionRef = useRef(null);
    const shouldAutoScrollRef = useRef(true);
    const isInitialLoadRef = useRef(true);
    /** Merge source for history reload — cleared synchronously on auth session reset so stale UI cannot resurrect `chat_id`. */
    const messagesMergeRef = useRef([]);
    const loadChatHistoryRef = useRef(() => {});

    useEffect(() => {
      messagesMergeRef.current = messages;
    }, [messages]);

    React.useEffect(() => {
      if (embeddedExploreTv && typeof window !== 'undefined' && window.SYLVAN_TECH_THEME?.ensureKeyframes) {
        window.SYLVAN_TECH_THEME.ensureKeyframes();
      }
    }, [embeddedExploreTv]);

    // Get profile from state store
    const [profile, setProfileState] = useState(() => {
      return window.SylvanFlowState ? window.SylvanFlowState.getProfile() : null;
    });

    // ✅ CRITICAL: Load profile early to ensure language is available for chat payloads
    // identity comes from auth.userKey ONLY
    const loadProfile = async () => {
      
      // Check if profile is already loaded in state
      if (window.SylvanFlowState && window.SylvanFlowState.isProfileLoaded()) {
        const existingProfile = window.SylvanFlowState.getProfile();
        if (existingProfile) {
          setProfileState(existingProfile);
          if (window.uiLogger) {
            window.uiLogger.uiLog(`🌐 [ASK-SYLVAN] Profile already loaded, language: ${window.SylvanFlowState.getLanguage()}`);
          }
          return;
        }
      }
      
      // Load profile if not already loaded
      try {
        // identity comes from auth.userKey ONLY
        // ✅ GATEWAY MIGRATION: Use SF_API_BASE (Gateway Worker) instead of same-origin /api/*
        const res = await window.SylvanFlowAuth.authenticatedFetch(
          `${SF_API_BASE}/api/profile/me`
        );
        
        window.SylvanFlowAuth.handleAuthResponse(res);
        
        // ✅ BUG 2: SYSTEM_MAP - Parse governed envelope (always JSON)
        let data;
        try {
          data = await res.json();
        } catch (jsonError) {
          if (window.uiLogger) {
            window.uiLogger.uiError('[BUG2] Profile API JSON parse error', {
              error: jsonError.message,
              httpStatus: res.status
            });
          } else {
            if (window.uiLogger) {
              window.uiLogger.uiError('[BUG2] Profile API JSON parse error', {
                error: jsonError.message,
                httpStatus: res.status
              });
            } else {
              console.error('[BUG2] Profile API JSON parse error', {
                error: jsonError.message,
                httpStatus: res.status
              });
            }
          }
          return;
        }
        
        // ✅ BUG 2: Canonical parse path - check data.data.profile (governedJson format)
        const profile = data.data?.profile || data.profile;
        
        if (data.ok === true && profile) {
          // ✅ BUG 2: Canonical photo source - prefer profile_photo_url
          let canonicalPhotoUrl = profile.profile_photo_url || profile.photo_url || null;
          const photoR2Key = profile.profile_photo_r2_key || null;
          
          // ✅ BUG 2: If missing but R2 key exists, construct gateway URL
          if (!canonicalPhotoUrl && photoR2Key) {
            canonicalPhotoUrl = `${SF_API_BASE}/api/profile-photo/${encodeURIComponent(photoR2Key)}`;
            // Update profile object for future use
            profile.profile_photo_url = canonicalPhotoUrl;
            if (window.uiLogger) {
              window.uiLogger.uiLog('[BUG2] Constructed profile photo URL from R2 key (on load):', canonicalPhotoUrl.substring(0, 80) + '...');
            }
          }
          
          if (window.uiLogger) {
            window.uiLogger.uiLog('[BUG2] Profile loaded from /api/profile/me', {
              authChecked: true,
              profileLoaded: true,
              userPhotoUrl: canonicalPhotoUrl ? canonicalPhotoUrl.substring(0, 50) + '...' : null,
              profile_photo_r2_key: photoR2Key ? photoR2Key.substring(0, 20) + '...' : null,
              avatarFallbackReason: !canonicalPhotoUrl ? 'no_photo_url' : null,
              canonicalField: profile.profile_photo_url ? 'profile_photo_url' : (profile.photo_url ? 'photo_url' : (photoR2Key ? 'profile_photo_r2_key (constructed)' : 'none'))
            });
          }
          
          // Update state store (this will set language)
          if (window.SylvanFlowState) {
            window.SylvanFlowState.setProfile(profile);
            setProfileState(profile);
            if (window.uiLogger) {
              window.uiLogger.uiLog(`🌐 [ASK-SYLVAN] Profile loaded, language: ${window.SylvanFlowState.getLanguage()}`);
            }
          }
        } else {
          // ✅ BUG 2: Log API error (governed envelope)
          if (window.uiLogger) {
            window.uiLogger.uiError('[BUG2] Profile API returned ok:false', {
              authChecked: true,
              profileLoaded: false,
              parsedOk: data.ok,
              hasProfile: !!profile,
              code: data.code || null
            });
          }
        }
      } catch (e) {
        if (window.uiLogger) {
          window.uiLogger.uiWarn(`⚠️ [ASK-SYLVAN] Failed to load profile: ${e.message}`);
        }
      } finally {
        // Set readiness flag once profile fetch completes (success or failure)
        window.__SF_READY__ = window.__SF_READY__ || {};
        window.__SF_READY__.askSylvan = true;
      }
    };

    // Listen for profile changes in state store
    useEffect(() => {
      if (!window.SylvanFlowState) {
        // If no state store, readiness will be set by loadChatHistory
        return;
      }
      
      const handleProfileChange = (newProfile) => {
        setProfileState(newProfile);
      };
      
      window.SylvanFlowState.on('profile', handleProfileChange);
      
      // ✅ CRITICAL: Load profile immediately if not in state store
      if (!window.SylvanFlowState.isProfileLoaded()) {
        loadProfile();
      }
      // If profile already loaded, readiness will be set by loadChatHistory
      
      return () => {
        window.SylvanFlowState.off('profile', handleProfileChange);
      };
    }, [profile]); // identity comes from auth.userKey ONLY

    // Initial load
    useEffect(() => {
      loadChatHistory();
      getCurrentLocation();
    }, []);

    // ✅ Reload chat history when view becomes 'ask-sylvan' (re-entry from other pages)
    useEffect(() => {
      if (!window.appState) return;
      
      let isInitialMount = true;
      let previousView = null;
      
      const handleStateChange = () => {
        const currentState = window.appState.get();
        const currentView = currentState?.view;
        
        // Skip initial mount (handled by initial load useEffect above)
        if (isInitialMount) {
          isInitialMount = false;
          previousView = currentView;
          return;
        }
        
        // If view changed to 'ask-sylvan' from another view, reload history
        if (currentView === 'ask-sylvan' && previousView !== 'ask-sylvan' && previousView !== null) {
          if (window.uiLogger) {
            window.uiLogger.uiLog(`🔄 [ASK-SYLVAN] View changed to 'ask-sylvan' from '${previousView}', reloading chat history`);
          }
          loadChatHistory();
        }
        
        previousView = currentView;
      };
      
      // Listen for appState changes
      if (window.appState.on) {
        window.appState.on('change', handleStateChange);
      } else {
        // Fallback: Poll appState if 'on' method not available
        const interval = setInterval(() => {
          handleStateChange();
        }, 1000);
        
        return () => clearInterval(interval);
      }
      
      return () => {
        if (window.appState && window.appState.off) {
          window.appState.off('change', handleStateChange);
        }
      };
    }, []);

    // ✅ TIGHTENED READINESS: Set flag only when input exists, is enabled, AND auth token is present
    useEffect(() => {
      const checkReadiness = () => {
        // Check 1: Input exists and is enabled/editable
        const input = inputRef.current;
        const inputExists = !!input;
        const inputEnabled = input && !input.disabled && !input.readOnly;
        
        // Check 2: Auth token is present in localStorage
        const sessionToken = localStorage.getItem('sylvanflow_session_token');
        const hasAuthToken = !!sessionToken;
        
        // Check 3: Loading is complete (chat history loaded)
        const isLoaded = !loading;
        
        if (inputExists && inputEnabled && hasAuthToken && isLoaded) {
          window.__SF_READY__ = window.__SF_READY__ || {};
          window.__SF_READY__.askSylvan = true;
          if (window.uiLogger) {
            window.uiLogger.uiLog('✅ [ASK-SYLVAN] Readiness flag set: input ready, auth token present, loading complete');
          }
        } else {
          // Reset flag if conditions not met
          if (window.__SF_READY__) {
            window.__SF_READY__.askSylvan = false;
          }
        }
      };
      
      // Check immediately and on state changes
      checkReadiness();
      
      // Also check periodically until ready (max 5 seconds)
      let attempts = 0;
      const maxAttempts = 50; // 50 * 100ms = 5 seconds
      const interval = setInterval(() => {
        attempts++;
        checkReadiness();
        if (window.__SF_READY__?.askSylvan || attempts >= maxAttempts) {
          clearInterval(interval);
        }
      }, 100);
      
      return () => clearInterval(interval);
    }, [loading, sending]); // Re-check when loading or sending (input lock) changes

    // Polling for new messages
    useEffect(() => {
      // ✅ FIX: Don't poll if we don't have a chatId or if we're still loading initial history
      if (!chatId || loading) {
        if (window.uiLogger) {
          window.uiLogger.uiLog(`⏸️ [ASK-SYLVAN] Polling paused: chatId=${!!chatId}, loading=${loading}`);
        }
        return;
      }
      
      if (window.uiLogger) {
        window.uiLogger.uiLog(`🔄 [ASK-SYLVAN] Starting message polling (chatId: ${chatId ? chatId.substring(0, 20) + '...' : 'none'}, lastMessageTime: ${lastMessageTime || 'none'})`);
      }
      
      const interval = setInterval(() => {
        checkForNewMessages();
      }, 5000); // ✅ FIX: Increased interval to 5s to reduce load and prevent rapid loops
      
      return () => {
        if (window.uiLogger) {
          window.uiLogger.uiLog(`⏸️ [ASK-SYLVAN] Stopping message polling`);
        }
        clearInterval(interval);
      };
    }, [chatId, lastMessageTime, loading]); // ✅ FIX: Include loading in dependencies to pause polling during initial load

    // Auto-scroll to bottom when messages change or typing indicator appears
    useEffect(() => {
      if (messages.length > 0 && (shouldAutoScrollRef.current || typing || isInitialLoadRef.current)) {
        // Use setTimeout to ensure DOM is updated
        setTimeout(() => {
          scrollToBottom(isInitialLoadRef.current);
          isInitialLoadRef.current = false;
        }, 100);
      }
    }, [messages, typing]);

    // Scroll handling
    useEffect(() => {
      if (chatContainerRef.current) {
        const container = chatContainerRef.current;
        const handleScroll = () => {
          const scrollTop = container.scrollTop;
          const scrollHeight = container.scrollHeight;
          const clientHeight = container.clientHeight;
          const distanceFromBottom = scrollHeight - scrollTop - clientHeight;
          shouldAutoScrollRef.current = distanceFromBottom < 200;
        };
        
        container.addEventListener('scroll', handleScroll);
        return () => container.removeEventListener('scroll', handleScroll);
      }
    }, []);

    const scrollToBottom = (immediate = false) => {
      if (chatContainerRef.current) {
        const container = chatContainerRef.current;
        // For initial load, scroll immediately without animation
        if (immediate) {
          container.scrollTop = container.scrollHeight;
        } else {
          // For new messages, use smooth scroll
          container.scrollTop = container.scrollHeight;
          // Also use scrollIntoView as fallback
          if (messagesEndRef.current) {
            messagesEndRef.current.scrollIntoView({ behavior: "smooth" });
          }
        }
      }
    };

    const getCurrentLocation = () => {
      return new Promise((resolve, reject) => {
        if (navigator.geolocation) {
          navigator.geolocation.getCurrentPosition(
            (position) => {
              const location = {
                lat: position.coords.latitude,
                lng: position.coords.longitude,
                accuracy: position.coords.accuracy,
              };
              setCurrentLocation(location);
              if (window.uiLogger) {
                window.uiLogger.uiLog(`📍 [ASK-SYLVAN] Location obtained (lat: ${location?.lat}, lng: ${location?.lng})`);
              }
              resolve(location);
            },
            (error) => {
              if (window.uiLogger) {
                window.uiLogger.uiWarn(`⚠️ [ASK-SYLVAN] Geolocation error:`, error.message);
              }
              reject(error);
            },
            {
              enableHighAccuracy: true,
              timeout: 10000,
              maximumAge: 60000,
            }
          );
        } else {
          const error = new Error("Geolocation not supported");
          if (window.uiLogger) {
            window.uiLogger.uiWarn("⚠️ [ASK-SYLVAN] Geolocation not supported");
          }
          reject(error);
        }
      });
    };

    const loadChatHistory = async () => {
      if (loadingHistoryRef.current) {
        if (window.uiLogger) {
          window.uiLogger.uiLog("⏸️ [ASK-SYLVAN] Chat history load already in progress, skipping");
        }
        return;
      }
      
      if (chatContainerRef.current) {
        scrollPositionRef.current = chatContainerRef.current.scrollTop;
      }
      
      try {
        loadingHistoryRef.current = true;
        setLoading(true);
        
        // identity comes from auth.userKey ONLY
        // Session token is required for authenticatedFetch, so identity is guaranteed
        const historyUrl = `${SF_API_BASE}/api/chat/history?limit=10`;
        // ✅ SECURITY: Admin-only logs (contains endpoint info)
        if (window.uiLogger) {
          window.uiLogger.uiLog(`📡 [ASK-SYLVAN] Loading chat history (identity from Bearer token)`);
          window.uiLogger.uiLog(`📡 [ASK-SYLVAN] History endpoint URL: ${historyUrl}`);
        }
        // ✅ Limit to latest 10 messages
        // identity comes from auth.userKey ONLY
        // ✅ SYSTEM_MAP COMPLIANT: Uses same-origin /api/* route (proxied via Pages Functions)
        // ✅ PART 1 FIX: Properly catch fetch failures (TypeError: Failed to fetch)
        const res = await window.SylvanFlowAuth.authenticatedFetch(historyUrl);
        window.SylvanFlowAuth.handleAuthResponse(res);

        if (window.uiLogger) {
          window.uiLogger.uiLog(`📡 [ASK-SYLVAN] Chat history response status: ${res.status}`);
        }

        if (res.ok) {
          // ✅ RUNTIME GUARD: Check Content-Type before parsing JSON
          const contentType = res.headers.get('content-type') || '';
          if (!contentType.includes('application/json')) {
            const bodyText = await res.clone().text().catch(() => '');
            const errorMsg = `Chat history endpoint returned non-JSON (status: ${res.status}, content-type: ${contentType}, body preview: ${bodyText.substring(0, 120)})`;
            if (window.uiLogger) {
              window.uiLogger.uiError(`❌ [ASK-SYLVAN] ${errorMsg}`);
            } else {
              console.error(`❌ [ASK-SYLVAN] ${errorMsg}`);
            }
            throw new Error(errorMsg);
          }
          
          const data = await res.json();
          // ✅ SECURITY: Do NOT log full chat history data (contains messages)
          if (window.uiLogger) {
            window.uiLogger.uiLog(`📡 [ASK-SYLVAN] Chat history response received (ok: ${data.ok}, messageCount: ${data.messages?.length || 0})`);
          }
          
          if (data.ok) {
            let messageArray = data.messages || [];
            if (window.uiLogger) {
              window.uiLogger.uiLog(`📡 [ASK-SYLVAN] Messages array (before filtering):`, messageArray.length);
            }
            // ✅ BUG TRIAGE: Log first message details for debugging (admin-only, no content)
            if (messageArray.length > 0) {
              const firstMsg = messageArray[0];
              const firstMsgRole = firstMsg.type || (firstMsg.input_message ? 'user' : firstMsg.ai_reply ? 'assistant' : 'system');
              if (window.uiLogger) {
                window.uiLogger.uiLog(`📡 [ASK-SYLVAN] First message: role=${firstMsgRole}, hasContent=${!!(firstMsg.text || firstMsg.input_message || firstMsg.ai_reply)}`);
              }
            } else {
              if (window.uiLogger) {
                window.uiLogger.uiLog(`📡 [ASK-SYLVAN] No messages returned from backend`);
              }
            }
            
            // ✅ Group messages into conversations (1 conversation = user message + AI response)
            // Process all messages returned (already limited to 10 by backend)
            const conversations = [];
            let currentConversation = [];
            
            // Process messages from the end (newest first) to group into conversations
            for (let i = messageArray.length - 1; i >= 0; i--) {
              const msg = messageArray[i];
              const msgType = msg.type || (msg.input_message ? 'user' : msg.ai_reply ? 'assistant' : 'system');
              
              if (msgType === 'user') {
                // User message - start a new conversation
                if (currentConversation.length > 0) {
                  // Save previous conversation (reverse it back to chronological order)
                  conversations.push([...currentConversation].reverse());
                }
                currentConversation = [msg];
              } else if (msgType === 'assistant') {
                // Assistant message - add to current conversation
                currentConversation.push(msg);
              }
              // Skip system messages
            }
            
            // Add the last conversation if it exists
            if (currentConversation.length > 0) {
              conversations.push([...currentConversation].reverse());
            }
            
            // Flatten conversations back to message array (oldest first)
            // Reverse conversations array to get chronological order
            messageArray = conversations.reverse().flat();
            
            if (window.uiLogger) {
              window.uiLogger.uiLog(`📡 [ASK-SYLVAN] Messages array (after grouping into conversations):`, messageArray.length, `conversations:`, conversations.length);
            }
            
            // ✅ STEP D: Implement merge strategy (don't overwrite if local has newer optimistic messages)
            const localForMerge = messagesMergeRef.current || [];
            const previousMessageCount = localForMerge.length;
            const uiLocalMessageCount = localForMerge.length;
            const uiLastLocalMsg = localForMerge.length > 0 ? localForMerge[localForMerge.length - 1] : null;
            const uiLastLocalMsgType = uiLastLocalMsg?.type || null;
            const uiLastLocalTs = uiLastLocalMsg?.timestamp || null;
            
            // Check if local messages are newer than server messages
            const serverLatestTs = messageArray.length > 0 
              ? (new Date(messageArray[messageArray.length - 1]?.timestamp || messageArray[messageArray.length - 1]?.created_at_utc || 0).getTime())
              : 0;
            const localLatestTs = uiLastLocalTs 
              ? (new Date(uiLastLocalTs).getTime())
              : 0;
            const uiLocalNewerThanServer = localLatestTs > serverLatestTs;
            
            let mergedMessages = messageArray;
            let mergeStrategyUsed = "overwrite";
            let uiHistoryReloadApplied = false;
            
            // ✅ STEP D: Merge if local has newer optimistic messages
            if (uiLocalNewerThanServer && localForMerge.length > 0) {
              // Merge: Keep server messages, append local messages that are newer
              const localNewerMessages = localForMerge.filter(msg => {
                const msgTs = new Date(msg.timestamp || msg.created_at_utc || 0).getTime();
                return msgTs > serverLatestTs;
              });
              mergedMessages = [...messageArray, ...localNewerMessages];
              mergeStrategyUsed = "merge";
              uiHistoryReloadApplied = true;
              if (window.uiLogger) {
                window.uiLogger.uiLog(`🧪 [UI] Merge strategy: kept ${messageArray.length} server + ${localNewerMessages.length} local newer messages`);
              }
            } else {
              mergeStrategyUsed = "overwrite";
              uiHistoryReloadApplied = true;
            }
            
            // ✅ STEP D: UI audit log (admin-only, no message content)
            if (window.uiLogger) {
              window.uiLogger.uiLog(`🧪 [UI] message_merge:`, {
                ui_local_message_count: uiLocalMessageCount,
                ui_last_local_msg_type: uiLastLocalMsgType,
                ui_history_reload_applied: uiHistoryReloadApplied,
                ui_merge_strategy_used: mergeStrategyUsed,
                ui_local_newer_than_server: uiLocalNewerThanServer,
                server_message_count: messageArray.length,
                merged_message_count: mergedMessages.length
              });
            }
            
            const hasNewMessages = mergedMessages.length > previousMessageCount;
            setMessages(mergedMessages);
            
            if (messageArray.length > 0) {
              const latestMessage = messageArray[messageArray.length - 1];
              const latestTime = latestMessage.timestamp || latestMessage.created_at_utc;
              if (latestTime) {
                setLastMessageTime(latestTime);
                if (window.uiLogger) {
                  window.uiLogger.uiLog(`📡 [ASK-SYLVAN] Last message time updated: ${latestTime}`);
                }
              }
            }
            
            // ✅ FIX: Use chat_id from backend as-is (backend returns correct format)
            if (data.chat_id && data.chat_id !== chatId) {
              setChatId(data.chat_id);
              if (window.uiLogger) {
                window.uiLogger.uiLog(`📡 [ASK-SYLVAN] Chat ID set from backend: ${data.chat_id ? data.chat_id.substring(0, 20) + '...' : 'none'}`);
              }
            }
            
            setTimeout(() => {
              if (chatContainerRef.current) {
                if (hasNewMessages && shouldAutoScrollRef.current) {
                  scrollToBottom(true); // Immediate scroll for initial load
                } else if (scrollPositionRef.current !== null && !hasNewMessages) {
                  chatContainerRef.current.scrollTop = scrollPositionRef.current;
                  scrollPositionRef.current = null;
                }
              }
            }, 100);
            
            if (window.uiLogger) {
              window.uiLogger.uiLog(`📡 [ASK-SYLVAN] Loaded ${messageArray.length} messages`);
            }
          } else {
            if (window.uiLogger) {
              window.uiLogger.uiWarn("⚠️ [ASK-SYLVAN] Response not ok:", { ok: data.ok, hasMessages: !!data.messages });
            }
            setMessages([]);
          }
        } else {
          if (window.uiLogger) {
            window.uiLogger.uiWarn(`⚠️ [ASK-SYLVAN] Chat history response not ok: ${res.status}`);
          }
          setMessages([]);
        }
      } catch (e) {
        // ✅ PART 1 FIX: Handle network failures gracefully (TypeError: Failed to fetch)
        // This is expected if network is unavailable or endpoint is down
        if (window.uiLogger) {
          window.uiLogger.uiWarn(`⚠️ [ASK-SYLVAN] Failed to load chat history: ${e.message}`);
        }
        setMessages([]); // Set empty array on failure
        // Don't throw - allow UI to continue with empty history
      } finally {
        if (window.uiLogger) {
          window.uiLogger.uiLog("✅ [ASK-SYLVAN] Setting loading to false");
        }
        setLoading(false);
        loadingHistoryRef.current = false;
        // Readiness flag is set separately in useEffect that checks input + auth
      }
    };

    loadChatHistoryRef.current = loadChatHistory;

    useEffect(() => {
      const ev =
        (typeof window !== 'undefined' &&
          window.SylvanFlowAuth &&
          window.SylvanFlowAuth.CHAT_SESSION_RESET_EVENT) ||
        'sylvanflow-chat-session-reset';
      const onReset = () => {
        messagesMergeRef.current = [];
        setChatId(null);
        setMessages([]);
        setLastMessageTime(null);
        loadingHistoryRef.current = false;
        queueMicrotask(() => {
          const fn = loadChatHistoryRef.current;
          if (typeof fn === 'function') fn();
        });
      };
      window.addEventListener(ev, onReset);
      return () => window.removeEventListener(ev, onReset);
    }, []);

    const checkForNewMessages = async () => {
      // ✅ FIX: Prevent checking while loading history (avoids infinite loop)
      if (loadingHistoryRef.current || loading) {
        if (window.uiLogger) {
          window.uiLogger.uiLog(`⏸️ [ASK-SYLVAN] Skipping status check - history load in progress`);
        }
        return;
      }
      
      // ✅ STEP 1A: Debug log before fetch
      if (window.uiLogger) {
        window.uiLogger.uiLog("🧪 [ASK] STATUS poll", { 
          hasChatId: !!chatId, 
          hasLastMessageTime: !!lastMessageTime, 
          loading, 
          loadingHistory: loadingHistoryRef.current 
        });
      }
      
      // identity comes from auth.userKey ONLY
      // chat_id is optional - backend will return it if needed
      try {
        // ✅ GATEWAY MIGRATION: Use SF_API_BASE (Gateway Worker) instead of same-origin /api/*
        // ✅ SYSTEM_MAP COMPLIANT: No URL identity params (identity via Authorization header only)
        // ✅ STEP 1: Removed chat_id from URL - backend derives from Bearer token
        let statusUrl = `${SF_API_BASE}/api/chat/status`;
        if (lastMessageTime) {
          statusUrl += `?last_message_time=${encodeURIComponent(lastMessageTime)}`;
          if (window.uiLogger) {
            window.uiLogger.uiLog(`🔍 [ASK-SYLVAN] Checking for new messages since: ${lastMessageTime}`);
          }
        } else {
          if (window.uiLogger) {
            window.uiLogger.uiLog(`🔍 [ASK-SYLVAN] Checking for new messages (no lastMessageTime set)`);
          }
        }
        
        const res = await window.SylvanFlowAuth.authenticatedFetch(statusUrl);
        
        window.SylvanFlowAuth.handleAuthResponse(res);
        
        if (res.ok) {
          const data = await res.json();
          
          // ✅ STEP 1A: Debug log after status JSON (admin-only, sanitized - no message content)
          if (window.uiLogger) {
            window.uiLogger.uiLog("🧪 [ASK] STATUS json", { 
              ok: data.ok, 
              newMessageCount: data.new_messages?.length || 0,
              hasChatId: !!data.chat_id
            });
          }
          
          // ✅ STEP 3: Fix for Failure Mode D - chat_id churn guard
          if (data.chat_id && !chatId) {
            setChatId(data.chat_id);
          } else if (data.chat_id && chatId && data.chat_id !== chatId) {
            if (window.uiLogger) {
              window.uiLogger.uiWarn("🧪 [ASK] chat_id changed mid-thread — ignoring", { 
                oldChatIdPrefix: chatId ? chatId.substring(0, 20) + '...' : 'none',
                newChatIdPrefix: data.chat_id ? data.chat_id.substring(0, 20) + '...' : 'none'
              });
            }
          }
          
          // ✅ FIX: Only reload if there are actually NEW messages (not just has_new flag)
          if (data.ok && data.new_messages && Array.isArray(data.new_messages) && data.new_messages.length > 0) {
            // ✅ STEP 3: Fix for Failure Mode C - Remove strict lastMessageTime gating
            // Allow reload if lastMessageTime is null (after send) OR if messages are >= lastMessageTime
            const lastTime = lastMessageTime ? new Date(lastMessageTime).getTime() : 0;
            const hasTrulyNewMessages = data.new_messages.some(msg => {
              const msgTime = msg.timestamp || msg.created_at_utc;
              if (!msgTime) return false; // Skip messages without timestamps
              const msgTimeNum = new Date(msgTime).getTime();
              // ✅ FIX: Use >= instead of > (lastTime + 1000) to accept messages at same timestamp
              return msgTimeNum >= lastTime;
            });
            
            if (hasTrulyNewMessages || !lastMessageTime) {
              if (window.uiLogger) {
                window.uiLogger.uiLog(`🔄 [ASK-SYLVAN] Found ${data.new_messages.length} new messages (lastMessageTime: ${lastMessageTime || 'null'}), reloading history`);
              }
              loadChatHistory();
            } else {
              if (window.uiLogger) {
                window.uiLogger.uiLog(`⏸️ [ASK-SYLVAN] Status check found ${data.new_messages.length} messages, but none are >= lastMessageTime (${lastMessageTime}), skipping reload`);
              }
            }
          } else {
            // No new messages - this is normal, don't log
            // console.log(`✅ [ASK-SYLVAN] No new messages`);
          }
        }
      } catch (e) {
        if (window.uiLogger) {
          window.uiLogger.uiWarn("⚠️ [ASK-SYLVAN] Failed to check for new messages:", e.message || e);
        }
      }
    };

    // ✅ STEP 3: waitForAssistantReply helper for async replies
    const waitForAssistantReply = async ({ timeoutMs = 12000, intervalMs = 1500, sinceTimestamp } = {}) => {
      const start = Date.now();
      while (Date.now() - start < timeoutMs) {
        let statusUrl = `${SF_API_BASE}/api/chat/status`;
        if (sinceTimestamp) statusUrl += `?last_message_time=${encodeURIComponent(sinceTimestamp)}`;

        const res = await window.SylvanFlowAuth.authenticatedFetch(statusUrl);
        window.SylvanFlowAuth.handleAuthResponse(res);

        if (res.ok) {
          const data = await res.json();
          if (data?.ok && Array.isArray(data?.new_messages) && data.new_messages.length > 0) {
            const additions = data.new_messages
              .filter(m => m.type === "assistant" || m.ai_reply)
              .map(m => ({
                id: `ai-${Date.now()}-${Math.random().toString(16).slice(2)}`,
                type: "assistant",
                text: m.text || m.ai_reply,
                timestamp: m.timestamp || m.created_at_utc || new Date().toISOString(),
              }));

            if (additions.length) {
              setMessages(prev => [...prev, ...additions]);
              const newestTs = additions[additions.length - 1].timestamp;
              setLastMessageTime(newestTs);
              shouldAutoScrollRef.current = true;
              return true;
            }
          }
        }
        await new Promise(r => setTimeout(r, intervalMs));
      }

      // ✅ SYS_MESSAGES_KV: Fetch timeout message from KV
      const timeoutMessage = await (typeof window !== 'undefined' && window.getSysMessage
        ? window.getSysMessage('ui:chat_timeout')
        : null) || "Sylvan didn't respond. Please try again.";
      setMessages(prev => [...prev, {
        id: `sys-timeout-${Date.now()}`,
        type: "system",
        text: timeoutMessage,
        timestamp: new Date().toISOString(),
      }]);
      return false;
    };

    const sendMessage = async () => {
      if (!inputText.trim() || sending) return;

      const userMessage = inputText.trim();
      setInputText("");
      setSending(true);
      setTyping(true);

      const newUserMessage = {
        id: `user-${Date.now()}`,
        type: "user",
        text: userMessage,
        timestamp: new Date().toISOString(),
      };
      setMessages(prev => [...prev, newUserMessage]);
      setLastMessageTime(newUserMessage.timestamp);
      
      // ✅ STEP 1A: Debug log at start (admin-only, sanitized - no message text)
      if (window.uiLogger) {
        window.uiLogger.uiLog("🧪 [ASK] SEND start", { 
          messageLength: userMessage.length,
          hasChatId: !!chatId, 
          lastMessageTime: newUserMessage.timestamp,
          ui_local_message_count: messages.length,
          ui_last_local_msg_type: messages.length > 0 ? messages[messages.length - 1]?.type : null,
          ui_last_local_ts: messages.length > 0 ? messages[messages.length - 1]?.timestamp : null
        });
      }

      try {
        // ✅ CRITICAL: Always include languageCode from state (never null/undefined)
        // This ensures backend uses profile language, not URL/lang params
        const languageCode = window.SylvanFlowState?.getLanguage() || 'en';
        
        const requestBody = {
          // identity comes from auth.userKey ONLY
          // ✅ SYSTEM_MAP COMPLIANT: Identity MUST come from Authorization header, not body
          message: userMessage,
          // ✅ FIX: Send chat_id if available (backend may ignore it, but it's useful for context)
          chat_id: chatId || null,
          languageCode: languageCode, // ✅ CRITICAL: Always send languageCode
        };
        
        if (window.uiLogger) {
          window.uiLogger.uiLog(`🌐 [ASK-SYLVAN] Sending message with languageCode: ${languageCode}`);
        }
        
        // ✅ Sync Location Toggle: Only send location if toggle is ON
        if (syncLocation) {
          // Try to get fresh location if not available or if we want to refresh
          let locationToUse = currentLocation;
          
          if (!locationToUse) {
            // Wait for location if not available
            try {
              if (window.uiLogger) {
                window.uiLogger.uiLog(`📍 [ASK-SYLVAN] Fetching fresh location before sending...`);
              }
              locationToUse = await getCurrentLocation();
            } catch (error) {
              if (window.uiLogger) {
                window.uiLogger.uiWarn(`⚠️ [ASK-SYLVAN] Failed to get location, sending without location:`, error.message);
              }
              locationToUse = null;
            }
          }
          
          if (locationToUse) {
            requestBody.latitude = locationToUse.lat;
            requestBody.longitude = locationToUse.lng;
            requestBody.location = {
              lat: locationToUse.lat,
              lng: locationToUse.lng,
              accuracy: locationToUse.accuracy,
            };
            // ✅ SECURITY: Never log full coordinates - only metadata
            if (window.uiLogger) {
              window.uiLogger.uiLog(`📍 [ASK-SYLVAN] Sending message with location (accuracy: ${locationToUse.accuracy?.toFixed(0) || 'unknown'}m)`);
            }
            setShowLocationPrompt(false); // Hide prompt if location obtained
          } else {
            if (window.uiLogger) {
              window.uiLogger.uiWarn(`⚠️ [ASK-SYLVAN] Location sync enabled but location unavailable, sending without location`);
            }
            // Show location prompt UI when location unavailable
            setShowLocationPrompt(true);
          }
        } else {
          if (window.uiLogger) {
            window.uiLogger.uiLog(`📍 [ASK-SYLVAN] Location sync disabled, sending without location`);
          }
        }
        
        // ✅ BUG 3: Log send attempt (admin-only, sanitized - no message text)
        if (window.uiLogger) {
          window.uiLogger.uiLog('[BUG3] sendMessage attempt', {
            messageLength: userMessage.length,
            hasChatId: !!chatId,
            chatIdPrefix: chatId ? chatId.substring(0, 20) + '...' : 'null',
            languageCode: languageCode,
            hasLocation: !!requestBody.location
          });
        }
        
        // ✅ SYSTEM_MAP COMPLIANT: Uses same-origin /api/* route (proxied via Pages Functions)
        let res;
        try {
          res = await window.SylvanFlowAuth.authenticatedFetch(
            `${SF_API_BASE}/api/chat/send`,
            {
              method: "POST",
              headers: {
                "Content-Type": "application/json",
              },
              body: JSON.stringify(requestBody),
            }
          );
        } catch (fetchError) {
          // ✅ BUG 3: Handle network errors (admin-only, sanitized - no message text)
          if (window.uiLogger) {
            window.uiLogger.uiError('[BUG3] sendMessage fetch error', {
              error: fetchError.message,
              messageLength: userMessage.length,
              offline: !navigator.onLine
            });
          } else {
            console.error('[BUG3] sendMessage fetch error', {
              error: fetchError.message,
              offline: !navigator.onLine
            });
          }
          setTyping(false);
          // ✅ BUG 3: Show governed error message
          // ✅ SYS_MESSAGES_KV: Fetch error messages from KV
          const sendFailedMsg = await (typeof window !== 'undefined' && window.getSysMessage
            ? window.getSysMessage('ui:send_failed')
            : null) || "Sorry, I couldn't send your message. Please try again.";
          const offlineMsg = await (typeof window !== 'undefined' && window.getSysMessage
            ? window.getSysMessage('ui:offline')
            : null) || "You're offline. Please check your connection and try again.";
          const errorMessage = {
            id: `error-${Date.now()}`,
            type: "system",
            text: navigator.onLine ? sendFailedMsg : offlineMsg,
            timestamp: new Date().toISOString(),
          };
          setMessages(prev => [...prev, errorMessage]);
          return;
        }
        
        window.SylvanFlowAuth.handleAuthResponse(res);

        // ✅ BUG 3: SYSTEM_MAP-compliant - Always parse JSON (governed envelope)
        // Known routes return HTTP 200 governed envelope, but defensive parsing for network errors
        let data;
        let requestId = null;
        
        try {
          // ✅ BUG 3: Always parse JSON when Content-Type is JSON (defensive)
          const contentType = res.headers.get("Content-Type") || "";
          if (contentType.includes("application/json")) {
            data = await res.json();
            requestId = data.requestId || res.headers.get("x-request-id") || null;
          } else {
            // Non-JSON response (shouldn't happen, but handle defensively)
            throw new Error(`Unexpected Content-Type: ${contentType}`);
          }
        } catch (jsonError) {
          // ✅ BUG 3: Handle JSON parse errors (defensive)
          if (window.uiLogger) {
            window.uiLogger.uiError('[CHAT_SEND] JSON parse error', {
              error: jsonError.message,
              httpStatus: res.status,
              statusText: res.statusText,
              messageLength: userMessage.length
            });
          } else {
            console.error('[CHAT_SEND] JSON parse error', {
              error: jsonError.message,
              httpStatus: res.status,
              statusText: res.statusText
            });
          }
          setTyping(false);
          // ✅ SYS_MESSAGES_KV: Fetch error message from KV
          const invalidResponseMsg = await (typeof window !== 'undefined' && window.getSysMessage
            ? window.getSysMessage('ui:invalid_response')
            : null) || "Sorry, I received an invalid response. Please try again.";
          const errorMessage = {
            id: `error-${Date.now()}`,
            type: "system",
            text: invalidResponseMsg,
            timestamp: new Date().toISOString(),
          };
          setMessages(prev => [...prev, errorMessage]);
          return;
        }
        
        // ✅ P0-2: Redact sensitive fields from response log
        const redactedData = { ...data };
        if (redactedData.token) delete redactedData.token;
        if (redactedData.session_token) delete redactedData.session_token;
        if (redactedData.access_token) delete redactedData.access_token;
        if (redactedData.auth_token) delete redactedData.auth_token;
        
        // ✅ STEP C: Extract requestId from multiple possible locations (FULL, never truncate for storage)
        const extractedRequestId = data.requestId || data.request_id || data.rid || requestId || res.headers.get("x-request-id") || null;
        
        // ✅ STEP D: Tighten UI parsing and log raw response on parse failure
        // Validate response has required fields
        const parsedOk = data.ok === true || data.ok === false; // Must be boolean
        const hasCode = !!data.code;
        const hasError = !!data.error;
        const hasRequestId = !!extractedRequestId;
        
        // ✅ P0-2: Structured log with full response (redact tokens)
        // ✅ MANDATORY ADDITION 1: Exact endpoint path
        const endpointPath = `${SF_API_BASE}/api/chat/send`;
        
        // ✅ SECURITY: Admin-only log, sanitized - no message text, no full response
        if (window.uiLogger) {
          window.uiLogger.uiLog('[CHAT_SEND]', {
            endpoint: endpointPath,
            requestId: extractedRequestId ? extractedRequestId.substring(0, 12) : null,
            httpStatus: res.status,
            parsedOk: parsedOk,
            ok: data.ok,
            code: data.code || null,
            hasCode,
            hasError,
            hasRequestId,
            messageLength: userMessage.length,
            responseKeyCount: Object.keys(data || {}).length
          });
        }
        
        // ✅ STEP D: Log raw response on parse failure (bounded to 500 chars) - admin-only
        if (!parsedOk) {
          if (window.uiLogger) {
            window.uiLogger.uiError('[CHAT_SEND] Invalid response format - ok is not boolean', {
              httpStatus: res.status,
              ok: data.ok,
              okType: typeof data.ok,
              responseKeyCount: Object.keys(data || {}).length
            });
          } else {
            console.error('[CHAT_SEND] Invalid response format - ok is not boolean', {
              httpStatus: res.status,
              ok: data.ok,
              okType: typeof data.ok
            });
          }
        }
        
        // ✅ P0-2: SYSTEM_MAP - Handle governed envelope (ok:false with HTTP 200)
        if (data.ok === false) {
          // ✅ P0-2: Ensure requestId and code are available for error tracking
          const errorRequestId = extractedRequestId;
          const errorCode = data.code || data.error_code || "UNKNOWN_ERROR";
          
          if (window.uiLogger) {
            window.uiLogger.uiError('[CHAT_SEND_FAILURE]', {
              requestId: errorRequestId,
              code: errorCode,
              httpStatus: res.status,
              error: data.error || null,
              message: data.message || null,
              fullResponse: redactedData
            });
          }
          
          // ✅ FLOW_INVESTIGATOR: Render governed error message with code and rid (SYSTEM_MAP requirement)
          // ✅ SYS_MESSAGES_KV: Fetch error message from KV (fallback to API error message)
          setTyping(false);
          const processFailedMsg = await (typeof window !== 'undefined' && window.getSysMessage
            ? window.getSysMessage('ui:process_failed')
            : null) || "Sorry, I couldn't process your message. Please try again.";
          const errorText = data.message || data.error || processFailedMsg;
          // ✅ STEP C: Display code and rid for user visibility (SYSTEM_MAP requirement)
          // Truncate requestId ONLY for display (never truncate for storage/logs)
          const errorDisplayText = errorCode && errorRequestId 
            ? `${errorText} (Code: ${errorCode}, ID: ${errorRequestId.substring(0, 12)})`
            : errorText;
          const errorMessage = {
            id: `error-${Date.now()}`,
            type: "system",
            text: errorDisplayText,
            code: errorCode,
            requestId: errorRequestId,
            timestamp: new Date().toISOString(),
          };
          setMessages(prev => {
            const beforeCount = prev.length;
            const afterMessages = [...prev, errorMessage];
            const afterCount = afterMessages.length;
            const lastMsg = afterMessages[afterMessages.length - 1];
            
            // ✅ BUG 3: UI no-drop proof - log merge result for ok:false path (admin-only, sanitized)
            if (window.uiLogger) {
              window.uiLogger.uiLog('[CHAT_UI_MERGE]', {
                beforeCount,
                afterCount,
                lastRole: lastMsg.type === 'assistant' ? 'assistant' : (lastMsg.type === 'user' ? 'user' : 'system'),
                lastType: lastMsg.type,
                mergeStrategy: 'error_append',
                hadServerReply: false
              });
            }
            
            return afterMessages;
          });
          return;
        }
        
        // ✅ BUG 3: Success path (data.ok === true)
        if (data.ok === true) {
          // ✅ STEP 3: Fix for Failure Mode D - chat_id churn guard
          if (data.chat_id && !chatId) {
            setChatId(data.chat_id);
          } else if (data.chat_id && chatId && data.chat_id !== chatId) {
            if (window.uiLogger) {
              window.uiLogger.uiWarn("🧪 [ASK] chat_id changed mid-thread — ignoring", { 
                oldChatIdPrefix: chatId ? chatId.substring(0, 20) + '...' : 'none',
                newChatIdPrefix: data.chat_id ? data.chat_id.substring(0, 20) + '...' : 'none'
              });
            }
          }
          
          if (data.ai_reply) {
            const aiReplyTimestamp = data.timestamp || new Date().toISOString();
            const aiReply = {
              id: `ai-${Date.now()}`,
              type: "assistant",
              text: data.ai_reply,
              timestamp: aiReplyTimestamp,
            };
            setMessages(prev => {
              const beforeCount = prev.length;
              const afterMessages = [...prev, aiReply];
              const afterCount = afterMessages.length;
              const lastMsg = afterMessages[afterMessages.length - 1];
              
              // ✅ BUG 3: UI no-drop proof - log merge result (admin-only, sanitized)
              if (window.uiLogger) {
                window.uiLogger.uiLog('[CHAT_UI_MERGE]', {
                  beforeCount,
                  afterCount,
                  lastRole: lastMsg.type === 'assistant' ? 'assistant' : (lastMsg.type === 'user' ? 'user' : 'system'),
                  lastType: lastMsg.type,
                  mergeStrategy: 'append',
                  hadServerReply: true
                });
              }
              
              return afterMessages;
            });
            setLastMessageTime(aiReplyTimestamp);
            setTyping(false);
            shouldAutoScrollRef.current = true;
          } else {
            // ✅ STEP 1A: Debug log for ok but no ai_reply (admin-only, sanitized)
            if (window.uiLogger) {
              window.uiLogger.uiWarn("🧪 [ASK] SEND ok BUT no ai_reply -> EXPECT ASYNC", { 
                hasChatId: !!chatId,
                chatIdPrefix: chatId ? chatId.substring(0, 20) + '...' : 'none',
                lastMessageTime: newUserMessage.timestamp 
              });
            }
            // ✅ STEP 3: Fix for Failure Mode A - Async reply handling
            setTyping(true);
            await waitForAssistantReply({ timeoutMs: 12000, intervalMs: 1500, sinceTimestamp: newUserMessage.timestamp });
            setTyping(false);
            
            // ✅ BUG 3: UI no-drop proof - log merge result after async reply
            setMessages(prev => {
              const beforeCount = prev.length;
              const lastMsg = prev[prev.length - 1];
              
              if (window.uiLogger) {
                window.uiLogger.uiLog('[CHAT_UI_MERGE]', {
                  beforeCount,
                  afterCount: prev.length,
                  lastRole: lastMsg?.type === 'assistant' ? 'assistant' : (lastMsg?.type === 'user' ? 'user' : 'system'),
                  lastType: lastMsg?.type || 'unknown',
                  mergeStrategy: 'async_poll',
                  hadServerReply: lastMsg?.type === 'assistant'
                });
              }
              
              return prev; // No change, just logging
            });
          }
        } else {
          // ✅ BUG 3: Defensive fallback - if data.ok is not explicitly true/false
          // This shouldn't happen with governed envelope, but handle defensively
          setTyping(false);
          if (window.uiLogger) {
            window.uiLogger.uiError('[CHAT_SEND] Unexpected response format', {
              httpStatus: res.status,
              parsedOk: data.ok,
              hasData: !!data,
              messageLength: userMessage.length
            });
          } else {
            console.error('[CHAT_SEND] Unexpected response format', {
              httpStatus: res.status,
              parsedOk: data.ok,
              hasData: !!data
            });
          }
          
          // ✅ SYS_MESSAGES_KV: Fetch error message from KV
          const unexpectedResponseMsg = await (typeof window !== 'undefined' && window.getSysMessage
            ? window.getSysMessage('ui:unexpected_response')
            : null) || "Sorry, I received an unexpected response. Please try again.";
          const errorMessage = {
            id: `error-${Date.now()}`,
            type: "system",
            text: unexpectedResponseMsg,
            timestamp: new Date().toISOString(),
          };
          setMessages(prev => {
            const beforeCount = prev.length;
            const afterMessages = [...prev, errorMessage];
            const afterCount = afterMessages.length;
            const lastMsg = afterMessages[afterMessages.length - 1];
            
            // ✅ BUG 3: UI no-drop proof - log merge result for unexpected format (admin-only)
            if (window.uiLogger) {
              window.uiLogger.uiLog('[CHAT_UI_MERGE]', {
                beforeCount,
                afterCount,
                lastRole: lastMsg.type === 'assistant' ? 'assistant' : (lastMsg.type === 'user' ? 'user' : 'system'),
                lastType: lastMsg.type,
                mergeStrategy: 'unexpected_format',
                hadServerReply: false
              });
            }
            
            return afterMessages;
          });
        }
      } catch (e) {
        setTyping(false);
        if (window.uiLogger) {
          window.uiLogger.uiError("Error sending message:", e.message || e);
        } else {
          console.error("Error sending message:", e);
        }
        // ✅ SYS_MESSAGES_KV: Fetch error message from KV
        const sendFailedMsg = await (typeof window !== 'undefined' && window.getSysMessage
          ? window.getSysMessage('ui:send_failed')
          : null) || "Failed to send message. Please try again.";
        setMessages(prev => {
          const beforeCount = prev.length;
          const afterMessages = [...prev, {
            id: `error-${Date.now()}`,
            type: "system",
            text: sendFailedMsg,
            timestamp: new Date().toISOString(),
          }];
          const afterCount = afterMessages.length;
          const lastMsg = afterMessages[afterMessages.length - 1];
          
          // ✅ BUG 3: UI no-drop proof - log merge result for exception path (admin-only)
          if (window.uiLogger) {
            window.uiLogger.uiLog('[CHAT_UI_MERGE]', {
              beforeCount,
              afterCount,
              lastRole: lastMsg.type === 'assistant' ? 'assistant' : (lastMsg.type === 'user' ? 'user' : 'system'),
              lastType: lastMsg.type,
              mergeStrategy: 'exception',
              hadServerReply: false
            });
          }
          
          return afterMessages;
        });
      } finally {
        setSending(false);
      }
    };

    const handleKeyPress = (e) => {
      // ✅ Issue #8: Input lock — ignore send while LLM/Ask Sylvan is processing
      if (sending) {
        e.preventDefault();
        return;
      }
      if (e.key === "Enter" && !e.shiftKey) {
        e.preventDefault();
        sendMessage();
      }
    };

    // ✅ CRITICAL: Linkify URLs in messages (make them clickable)
    // Supports both Markdown links [text](url) and plain URLs
    const linkifyMessage = (text, messageType = "assistant") => {
      if (!text || typeof text !== 'string') return text;
      
      const linkClassName = messageType === "user" 
        ? 'text-teal-100 underline hover:text-white' 
        : 'text-teal-600 underline hover:text-teal-700';
      
      // ✅ FIX: Helper function to detect in-app links (/app?tab=...)
      const isInAppLink = (url) => {
        try {
          // Handle both absolute and relative URLs
          const urlObj = url.startsWith('http') 
            ? new URL(url) 
            : new URL(url, window.location.origin);
          const isInApp = urlObj.pathname === '/app' && urlObj.searchParams.has('tab');
          // ✅ FIX: Removed console.log from render path - only log when link is clicked (in navigateToInAppTab)
          return isInApp;
        } catch (e) {
          // Only log errors, not every detection (errors are rare)
          if (window.uiLogger) {
            window.uiLogger.uiWarn('[ASK-SYLVAN] Failed to parse URL for in-app check:', url, e.message || e);
          }
          return false;
        }
      };
      
      // ✅ FIX: Helper function to navigate to in-app tab using appState
      const navigateToInAppTab = (url) => {
        try {
          // Handle both absolute and relative URLs
          const urlObj = url.startsWith('http') 
            ? new URL(url) 
            : new URL(url, window.location.origin);
          const tab = urlObj.searchParams.get('tab');
          if (!tab) {
            if (window.uiLogger) {
              window.uiLogger.uiWarn('[ASK-SYLVAN] No tab param in URL:', url);
            }
            return false;
          }
          
          // Extract all tab params
          const tabParams = {};
          for (const [key, value] of urlObj.searchParams.entries()) {
            if (key !== 'tab') {
              tabParams[key] = value;
            }
          }
          
          if (window.uiLogger) {
            window.uiLogger.uiLog(`[ASK-SYLVAN] Navigating to in-app tab: ${tab}`, tabParams);
          }
          
          // Use appState.set() for in-app navigation
          if (window.appState && window.appState.set) {
            window.appState.set({
              view: tab,
              tabParams: Object.keys(tabParams).length > 0 ? tabParams : null
            });
            if (window.uiLogger) {
              window.uiLogger.uiLog(`[ASK-SYLVAN] ✅ appState.set() called for tab: ${tab}`);
            }
            return true;
          } else {
            if (window.uiLogger) {
              window.uiLogger.uiError('[ASK-SYLVAN] window.appState.set() not available!');
            } else {
              console.error('[ASK-SYLVAN] window.appState.set() not available!');
            }
          }
        } catch (e) {
          if (window.uiLogger) {
            window.uiLogger.uiError('[ASK-SYLVAN] Failed to navigate to in-app tab:', url, e.message || e);
          } else {
            console.error('[ASK-SYLVAN] Failed to navigate to in-app tab:', url, e);
          }
        }
        return false;
      };
      
      // Helper function to process markdown bold in text
      const processMarkdownBold = (textContent) => {
        const boldPattern = /\*\*([^*]+)\*\*/g;
        const parts = [];
        let lastIndex = 0;
        let match;
        
        while ((match = boldPattern.exec(textContent)) !== null) {
          // Add text before the bold
          if (match.index > lastIndex) {
            parts.push({ type: 'text', content: textContent.substring(lastIndex, match.index) });
          }
          // Add the bold text
          parts.push({ type: 'bold', content: match[1] });
          lastIndex = boldPattern.lastIndex;
        }
        
        // Add remaining text after last bold
        if (lastIndex < textContent.length) {
          parts.push({ type: 'text', content: textContent.substring(lastIndex) });
        }
        
        return parts.length > 0 ? parts : [{ type: 'text', content: textContent }];
      };
      
      // First, handle Markdown links: [text](url)
      const markdownLinkPattern = /\[([^\]]+)\]\(([^)]+)\)/g;
      const markdownMatches = [];
      let match;
      let lastIndex = 0;
      const parts = [];
      
      // Find all Markdown links
      while ((match = markdownLinkPattern.exec(text)) !== null) {
        // Add text before the link
        if (match.index > lastIndex) {
          parts.push({ type: 'text', content: text.substring(lastIndex, match.index) });
        }
        // Add the link
        parts.push({ 
          type: 'markdown-link', 
          text: match[1], 
          url: match[2] 
        });
        lastIndex = markdownLinkPattern.lastIndex;
      }
      
      // Add remaining text after last link
      if (lastIndex < text.length) {
        parts.push({ type: 'text', content: text.substring(lastIndex) });
      }
      
      // If we found Markdown links, process them
      if (parts.some(p => p.type === 'markdown-link')) {
        const elements = [];
        parts.forEach((part, index) => {
          if (part.type === 'markdown-link') {
            // ✅ FIX: Check if this is an in-app link
            const isInApp = isInAppLink(part.url);
            
            // ✅ EMERGENCY UI: Style tel: links as buttons, other links with proper wrapping
            const isTelLink = part.url.startsWith('tel:');
            const isEmergencyLink = part.url.includes('emergency_id') || part.url.includes('tab=emergency');
            
            const linkStyle = isTelLink ? {
              display: 'inline-block',
              padding: '8px 16px',
              margin: '4px 2px',
              borderRadius: '20px',
              backgroundColor: '#DC2626',
              color: '#FFFFFF',
              textDecoration: 'none',
              fontWeight: 600,
              fontSize: '14px',
              textAlign: 'center',
              minWidth: '120px',
              whiteSpace: 'nowrap'
            } : {
              wordBreak: 'break-all',
              maxWidth: '100%',
              display: 'inline-block'
            };
            
            const linkClass = isTelLink 
              ? '' 
              : (messageType === "user" 
                  ? 'text-teal-100 underline hover:text-white' 
                  : 'text-teal-600 underline hover:text-teal-700');
            
            // Create clickable link with custom text
            elements.push(
              React.createElement('a', {
                key: `md-link-${index}`,
                href: part.url,
                target: isInApp ? undefined : '_blank',
                rel: isInApp ? undefined : 'noopener noreferrer',
                className: linkClass,
                style: linkStyle,
                onClick: (e) => {
                  e.preventDefault();
                  e.stopPropagation();
                  
                  if (isTelLink) {
                    // tel: links - let browser handle
                    // SF_ALLOW_HARDEXIT_REDIRECT - tel: links require window.location.href for phone dialing
                    window.location.href = part.url;
                  } else if (isInApp) {
                    // ✅ FIX: Use appState.set() for in-app navigation
                    navigateToInAppTab(part.url);
                  } else {
                    // External link - open in new tab
                    window.open(part.url, '_blank', 'noopener,noreferrer');
                  }
                }
              }, part.text)
            );
          } else {
            // Process markdown bold and plain URLs in text
            const boldParts = processMarkdownBold(part.content);
            boldParts.forEach((boldPart, boldIndex) => {
              if (boldPart.type === 'bold') {
                // Render bold text
                elements.push(
                  React.createElement('strong', {
                    key: `bold-${index}-${boldIndex}`,
                    className: 'font-bold'
                  }, boldPart.content)
                );
              } else {
                // Process plain URLs in text
                const urlPattern = /(https?:\/\/[^\s]+|www\.[^\s]+)/gi;
                const urlParts = boldPart.content.split(urlPattern);
                urlParts.forEach((urlPart, urlIndex) => {
                  if (urlPattern.test(urlPart)) {
                    let href = urlPart;
                    if (urlPart.startsWith('www.')) {
                      href = `https://${urlPart}`;
                    }
                    
                    // ✅ FIX: Check if this is an in-app link
                    const isInApp = isInAppLink(href);
                    
                    // ✅ EMERGENCY UI: Style URLs with proper wrapping
                    elements.push(
                      React.createElement('a', {
                        key: `url-link-${index}-${boldIndex}-${urlIndex}`,
                        href: href,
                        target: isInApp ? undefined : '_blank',
                        rel: isInApp ? undefined : 'noopener noreferrer',
                        className: linkClassName,
                        style: {
                          wordBreak: 'break-all',
                          maxWidth: '100%',
                          display: 'inline-block'
                        },
                        onClick: (e) => {
                          e.preventDefault();
                          e.stopPropagation();
                          
                          if (isInApp) {
                            // ✅ FIX: Use appState.set() for in-app navigation
                            navigateToInAppTab(href);
                          } else {
                            // External link - open in new tab
                            window.open(href, '_blank', 'noopener,noreferrer');
                          }
                        }
                      }, urlPart)
                    );
                  } else {
                    elements.push(urlPart);
                  }
                });
              }
            });
          }
        });
        return elements.length > 0 ? elements : text;
      }
      
      // No Markdown links found, handle markdown bold and plain URLs
      const boldParts = processMarkdownBold(text);
      const elements = [];
      
      boldParts.forEach((boldPart, boldIndex) => {
        if (boldPart.type === 'bold') {
          // Render bold text
          elements.push(
            React.createElement('strong', {
              key: `bold-${boldIndex}`,
              className: 'font-bold'
            }, boldPart.content)
          );
        } else {
          // Process plain URLs in text
          const urlPattern = /(https?:\/\/[^\s]+|www\.[^\s]+)/gi;
          const urlParts = boldPart.content.split(urlPattern);
          urlParts.forEach((urlPart, urlIndex) => {
            if (urlPattern.test(urlPart)) {
              let href = urlPart;
              if (urlPart.startsWith('www.')) {
                href = `https://${urlPart}`;
              }
              
              // ✅ FIX: Check if this is an in-app link
              const isInApp = isInAppLink(href);
              
              elements.push(
                React.createElement('a', {
                  key: `link-${boldIndex}-${urlIndex}`,
                  href: href,
                  target: isInApp ? undefined : '_blank',
                  rel: isInApp ? undefined : 'noopener noreferrer',
                  className: linkClassName,
                  onClick: (e) => {
                    e.preventDefault();
                    e.stopPropagation();
                    
                    if (isInApp) {
                      // ✅ FIX: Use appState.set() for in-app navigation
                      navigateToInAppTab(href);
                    } else {
                      // External link - open in new tab
                      window.open(href, '_blank', 'noopener,noreferrer');
                    }
                  }
                }, urlPart)
              );
            } else {
              elements.push(urlPart);
            }
          });
        }
      });
      
      return elements.length > 0 ? elements : text;
    };

    // ✅ STEP 1: Helper Functions
    // ✅ PROFILE-PHOTO: Use authenticated fetch + blob for profile photos (auth-only endpoint)
    const [userAvatarUrl, setUserAvatarUrl] = React.useState(null);
    const avatarObjectUrlRef = React.useRef(null);
    
    React.useEffect(() => {
      // Cleanup object URL on unmount
      return () => {
        if (avatarObjectUrlRef.current) {
          URL.revokeObjectURL(avatarObjectUrlRef.current);
          avatarObjectUrlRef.current = null;
        }
      };
    }, []);
    
    React.useEffect(() => {
      let cancelled = false;
      if (!profile) {
        setUserAvatarUrl(null);
        return undefined;
      }
      void (async () => {
        const url =
          window.SylvanProfilePhoto && typeof window.SylvanProfilePhoto.hydrate === 'function'
            ? await window.SylvanProfilePhoto.hydrate(profile, SF_API_BASE)
            : null;
        if (!cancelled) setUserAvatarUrl(url);
      })();
      return () => {
        cancelled = true;
      };
    }, [profile?.user_key, profile?.profile_photo_r2_key, profile?.profile_photo_url, profile?.photo_url]);

    const renderMessageBody = (rawText, msgType) => {
      if (!rawText || typeof rawText !== 'string') return rawText;
      const paragraphs = rawText.split('\n\n');
      return (
        <div>
          {paragraphs.map((paragraph, idx) => (
            <p 
              key={idx}
              style={{
                fontSize: '15px',
                lineHeight: 1.55,
                marginBottom: idx === paragraphs.length - 1 ? 0 : '10px'
              }}
            >
              {linkifyMessage(paragraph, msgType)}
            </p>
          ))}
        </div>
      );
    };

    const formatTimestamp = (timestamp) => {
      try {
        const date = new Date(timestamp);
        return date.toLocaleTimeString('en-US', { hour: 'numeric', minute: '2-digit', hour12: true });
      } catch {
        return "";
      }
    };

    // Navigation helper - only for hard exit pages
    const navigate = (path) => {
      // ✅ SYSTEM_MAP COMPLIANT: Only allow navigation to hard exit pages (no URL identity params)
      // Identity will be derived from Bearer token inside destination pages
      const hardExitPages = ['/legal', '/flow-finder', '/flow-plan-form', '/flow-tune', '/micro-action'];
      if (hardExitPages.some(page => path.startsWith(page))) {
        // SF_ALLOW_HARDEXIT_REDIRECT
        window.location.href = path;
      } else {
        if (window.uiLogger) {
          window.uiLogger.uiWarn('[ASK-SYLVAN] Navigation to internal route blocked. Use appState.set() instead.');
        }
      }
    };

    const emb = embeddedExploreTv === true;

    return (
      <div
        className={
          emb
            ? 'flex max-h-full min-h-0 w-full flex-1 flex-col overflow-hidden bg-[#070a0c] text-slate-100'
            : 'flex min-h-0 w-full flex-1 flex-col overflow-hidden bg-white'
        }
        style={
          emb
            ? {
                minHeight: 0,
                maxHeight: '100%',
                flex: '1 1 0%',
                display: 'flex',
                flexDirection: 'column',
                ...(embeddedPowerUpTv ? { height: '100%' } : {})
              }
            : { minHeight: 0, height: '100%', display: 'flex', flexDirection: 'column' }
        }
      >
        <style>
{`
.sf-chat-container-watermark { position: relative; }
.sf-chat-container-watermark::before{
  content:"";
  position:absolute;
  inset:0;
  pointer-events:none;
  opacity:0.05;
  background-image:url("/assets/sylvanflow-logo.png");
  background-repeat:no-repeat;
  background-position:center 80px;
  background-size:180px auto;
  filter:grayscale(100%);
  z-index:0;
}
.sf-chat-container-watermark-emb::before{
  opacity:0.07;
  background-position:center 45%;
  filter:grayscale(100%) drop-shadow(0 0 24px rgba(45,226,197,0.35));
}
.sf-chat-container-watermark > .sf-chat-layer{
  position:relative;
  z-index:1;
}
.sf-assistant-bubble-content{
  position:relative;
  z-index:1;
}
.sf-explore-embedded-ask-shell {
  box-shadow: inset 0 0 0 1px rgba(45,226,197,0.14), 0 0 44px -18px rgba(45,226,197,0.38);
}
strong{ font-weight:700; }
`}
        </style>
        <div
          className={
            emb
              ? 'sf-explore-embedded-ask-shell relative mx-0 mb-0 mt-0 flex min-h-0 min-w-0 flex-1 flex-col overflow-hidden rounded-lg border border-[#2DE2C5]/30 bg-[#05080a]/98'
              : 'mx-auto flex min-h-0 w-full max-w-2xl flex-1 flex-col overflow-hidden bg-white'
          }
          style={{
            display: 'flex',
            flexDirection: 'column',
            minHeight: 0,
            ...(emb ? { maxHeight: '100%' } : {})
          }}
        >
          {loading ? (
            <div
              className="flex flex-1 flex-col items-center justify-center"
              style={{ flex: '1 1 0%', minHeight: 0, overflow: 'hidden' }}
            >
              {emb ? (
                <>
                  <div className="mb-4 flex gap-1">
                    {[0, 1, 2, 3, 4].map((i) => (
                      <span
                        key={`ask-load-dot-${i}`}
                        className="h-1 w-4 rounded-full bg-gradient-to-r from-[#2DE2C5]/25 to-[#2DE2C5]/90"
                        style={{
                          animation: 'nuask-fray-pulse 1.05s ease-in-out infinite',
                          animationDelay: `${i * 0.1}s`
                        }}
                      />
                    ))}
                  </div>
                  <p className="font-mono text-[10px] uppercase tracking-[0.28em] text-[#2DE2C5]/75">Loading chat…</p>
                </>
              ) : (
                <p className="text-sm text-slate-500">Loading chat...</p>
              )}
            </div>
          ) : messages.length === 0 ? (
            <div
              className={
                emb
                  ? 'flex flex-1 flex-col items-center justify-center px-4 py-8'
                  : 'flex-1 flex flex-col items-center justify-center px-4 py-8'
              }
              style={{ flex: '1 1 0%', minHeight: 0, overflow: 'hidden', paddingLeft: '16px', paddingRight: '16px' }}
            >
              {/* Profile Picture + Welcome Message (matches Glide design) */}
              <div className="mb-6 flex flex-col sm:flex-row items-center sm:items-start gap-4 max-w-md">
                {profile && (() => {
                  if ((window.__DEV__ || location.hostname.includes('localhost')) && window.uiLogger) {
                    window.uiLogger.uiLog("[UI] user avatar src:", userAvatarUrl ? userAvatarUrl.substring(0, 50) + '...' : 'none');
                  }
                  return (
                    <div className="flex-shrink-0">
                      <div
                        className={
                          emb
                            ? 'flex h-16 w-16 items-center justify-center overflow-hidden rounded-full border border-[#2DE2C5]/45 bg-black/40 shadow-[0_0_28px_-8px_rgba(45,226,197,0.55)] sm:h-20 sm:w-20'
                            : 'flex h-16 w-16 items-center justify-center overflow-hidden rounded-full bg-teal-100 sm:h-20 sm:w-20'
                        }
                      >
                        {userAvatarUrl ? (
                          <img
                            src={userAvatarUrl}
                            alt={profile?.name || profile?.user_name || "User"}
                            className="w-full h-full object-cover rounded-full"
                            onError={(e) => {
                              e.target.style.display = 'none';
                              if (e.target.nextSibling) {
                                e.target.nextSibling.style.display = 'flex';
                              }
                            }}
                          />
                        ) : null}
                        <span 
                          className={
                            emb
                              ? 'flex text-2xl font-semibold text-[#2DE2C5]/90 sm:text-3xl'
                              : 'flex text-2xl text-teal-600 font-semibold sm:text-3xl'
                          }
                          style={{ display: userAvatarUrl ? 'none' : 'flex' }}
                        >
                          {(profile?.name || profile?.user_name || "U")[0].toUpperCase()}
                        </span>
                      </div>
                    </div>
                  );
                })()}
                <div className="flex-1 text-center sm:text-left">
                  <h2 className={emb ? 'mb-2 text-lg font-bold tracking-wide text-white sm:text-xl' : 'text-lg sm:text-xl font-bold text-slate-800 mb-2'}>
                    Let Sylvan help with your Flow
                  </h2>
                  <p className={emb ? 'text-sm text-[#2DE2C5]/80 sm:text-base' : 'text-sm sm:text-base text-slate-600'}>
                    Begin with a simple hello!
                  </p>
                </div>
              </div>
            </div>
          ) : (
            <div
              ref={chatContainerRef}
              data-testid="ask-sylvan-chat"
              className={`overflow-y-auto sf-chat-container-watermark ${emb ? 'sf-chat-container-watermark-emb min-h-0 flex-1' : 'min-h-0 flex-1'}`}
              style={
                emb
                  ? {
                      flex: '1 1 0%',
                      minHeight: 0,
                      height: '100%',
                      maxHeight: '100%',
                      overflowY: 'auto',
                      overflowX: 'hidden',
                      WebkitOverflowScrolling: 'touch',
                      position: 'relative',
                      paddingTop: '6px',
                      paddingBottom: '6px',
                      paddingLeft: '10px',
                      paddingRight: '10px'
                    }
                  : {
                      flex: '1 1 0%',
                      minHeight: 0,
                      overflowY: 'auto',
                      overflowX: 'hidden',
                      WebkitOverflowScrolling: 'touch',
                      position: 'relative',
                      paddingTop: '16px',
                      paddingBottom: '8px',
                      paddingLeft: '16px',
                      paddingRight: '16px'
                    }
              }
            >
              <div className="sf-chat-layer" style={{ display: 'flex', flexDirection: 'column', gap: '12px' }}>
                {messages.map((msg) => {
                  const isSystem = msg.type === "system";
                  const isUser = msg.type === "user";
                  const isAssistant = msg.type === "assistant";
                  
                  // ✅ STEP 10B: User message avatar (use memoized value)
                  const messageUserAvatarUrl = isUser && profile ? userAvatarUrl : null;
                  if (window.__DEV__ || location.hostname.includes('localhost') && messageUserAvatarUrl) {
                    if (window.uiLogger) {
                      window.uiLogger.uiLog("[UI] user avatar src:", messageUserAvatarUrl ? messageUserAvatarUrl.substring(0, 50) + '...' : 'none');
                    }
                  }
                  
                  return (
                    <div
                      key={msg.id}
                      data-testid={isAssistant ? "chat-message-ai" : isUser ? "chat-message-user" : "chat-message"}
                      className={`flex gap-2 ${isUser ? "justify-end items-start" : isSystem ? "justify-center items-start" : "justify-start items-start"}`}
                    >
                      {/* ✅ BUG 1 FIX: Assistant Avatar - Use SylvanFlow logo with React state fallback */}
                      {isAssistant && <AssistantAvatar nuAskEmbedded={emb} />}
                      
                      {/* ✅ STEP 10B: User Profile Photo */}
                      {isUser && profile && (
                        <div
                          className={
                            emb
                              ? 'flex h-8 w-8 shrink-0 items-center justify-center overflow-hidden rounded-full border border-[#2DE2C5]/30 bg-black/40 shadow-[0_0_16px_-6px_rgba(45,226,197,0.45)] sm:h-10 sm:w-10'
                              : 'flex h-8 w-8 shrink-0 items-center justify-center overflow-hidden rounded-full bg-teal-100 sm:h-10 sm:w-10'
                          }
                        >
                          {messageUserAvatarUrl ? (
                            <img
                              src={messageUserAvatarUrl}
                              alt="Profile"
                              className="w-full h-full object-cover rounded-full"
                              onError={(e) => {
                                e.target.style.display = 'none';
                                if (e.target.nextSibling) {
                                  e.target.nextSibling.style.display = 'flex';
                                }
                              }}
                            />
                          ) : null}
                          <span
                            className={
                              emb
                                ? 'flex text-base text-[#2DE2C5]/95 sm:text-lg'
                                : 'flex text-base text-teal-600 sm:text-lg'
                            }
                            style={{ display: userAvatarUrl ? 'none' : 'flex' }}
                          >
                            {profile.name?.[0]?.toUpperCase() || profile.user_name?.[0]?.toUpperCase() || '👤'}
                          </span>
                        </div>
                      )}
                      
                      <div
                        style={{
                          maxWidth: '78vw',
                          width: 'fit-content',
                          borderRadius: isSystem ? '10px' : '14px',
                          padding: isSystem ? '10px 12px' : '12px 14px',
                          ...(emb
                            ? {
                                background: isUser
                                  ? 'linear-gradient(145deg, rgba(13,148,136,0.95) 0%, rgba(15,118,110,0.98) 55%, rgba(17,94,89,1) 100%)'
                                  : isSystem
                                    ? 'rgba(15,23,42,0.94)'
                                    : '#11151d',
                                border: isUser
                                  ? '1px solid rgba(45,226,197,0.55)'
                                  : isSystem
                                    ? '1px solid rgba(148,163,184,0.35)'
                                    : '1px solid rgba(45,226,197,0.32)',
                                color: isUser ? '#f8fafc' : isSystem ? '#cbd5e1' : '#e5e7eb',
                                boxShadow: isUser
                                  ? '0 0 32px -8px rgba(45,226,197,0.6), inset 0 0 0 1px rgba(45,226,197,0.25)'
                                  : isAssistant
                                    ? '0 0 36px -10px rgba(45,226,197,0.45), inset 0 0 0 1px rgba(45,226,197,0.12)'
                                    : '0 0 16px -6px rgba(100,116,139,0.35)',
                                animation:
                                  isAssistant && !isSystem
                                    ? 'nuask-edge-refract-ring 11s ease-in-out infinite'
                                    : undefined
                              }
                            : {
                                background: isUser ? '#0F766E' : isSystem ? '#F3F4F6' : '#F8FAFC',
                                border: isSystem ? '1px solid #E5E7EB' : isAssistant ? '1px solid #E2E8F0' : 'none',
                                color: isUser ? '#fff' : isSystem ? '#374151' : '#0F172A',
                                boxShadow: isUser || isAssistant ? '0 1px 2px rgba(0,0,0,0.06)' : 'none'
                              }),
                          fontSize: isSystem ? '13px' : undefined,
                          position: isAssistant ? 'relative' : undefined
                        }}
                      >
                        {isAssistant && (
                          <div
                            style={{
                              position: 'absolute',
                              top: 0,
                              left: 0,
                              right: 0,
                              bottom: 0,
                              backgroundImage: 'url("/assets/sylvanflow-logo.png")',
                              backgroundRepeat: 'no-repeat',
                              backgroundPosition: 'center center',
                              backgroundSize: '120px auto',
                              opacity: emb ? 0.09 : 0.06,
                              filter: 'grayscale(100%)',
                              pointerEvents: 'none',
                              zIndex: 0,
                              borderRadius: '14px'
                            }}
                          />
                        )}
                        <div className={isAssistant ? "sf-assistant-bubble-content" : ""}>
                          {/* ✅ STEP 4: Assistant Text Readability */}
                          {isAssistant ? (
                            renderMessageBody(msg.text || msg.input_message || msg.ai_reply, msg.type)
                          ) : (
                            <p className="text-sm sm:text-base leading-relaxed whitespace-pre-wrap break-words">
                              {linkifyMessage(msg.text || msg.input_message || msg.ai_reply, msg.type)}
                            </p>
                          )}
                          
                          {/* ✅ STEP 7: Timestamp */}
                          {msg.timestamp && (
                            <p 
                              style={{
                                fontSize: "12px",
                                opacity: 0.55,
                                marginTop: "6px",
                                color: isUser ? "#fff" : undefined
                              }}
                            >
                              {formatTimestamp(msg.timestamp || msg.created_at_utc)}
                            </p>
                          )}
                        </div>
                      </div>
                    </div>
                  );
                })}
                {typing && (
                  <div className="flex justify-start">
                    <div
                      className={
                        emb
                          ? 'rounded-xl border border-[#2DE2C5]/25 bg-[#11151d] px-4 py-3 shadow-[0_0_22px_-10px_rgba(45,226,197,0.35)]'
                          : 'rounded-lg bg-slate-100 px-4 py-3'
                      }
                    >
                      <div className="flex gap-1">
                        <div
                          className={
                            emb
                              ? 'h-2 w-2 animate-bounce rounded-full bg-[#2DE2C5]/90 shadow-[0_0_8px_rgba(45,226,197,0.8)]'
                              : 'h-2 w-2 animate-bounce rounded-full bg-slate-400'
                          }
                          style={{ animationDelay: '0ms' }}
                        />
                        <div
                          className={
                            emb
                              ? 'h-2 w-2 animate-bounce rounded-full bg-[#2DE2C5]/85 shadow-[0_0_8px_rgba(45,226,197,0.65)]'
                              : 'h-2 w-2 animate-bounce rounded-full bg-slate-400'
                          }
                          style={{ animationDelay: '150ms' }}
                        />
                        <div
                          className={
                            emb
                              ? 'h-2 w-2 animate-bounce rounded-full bg-teal-300/90 shadow-[0_0_8px_rgba(45,226,197,0.55)]'
                              : 'h-2 w-2 animate-bounce rounded-full bg-slate-400'
                          }
                          style={{ animationDelay: '300ms' }}
                        />
                      </div>
                    </div>
                  </div>
                )}
                <div ref={messagesEndRef} />
              </div>
            </div>
          )}

          {/* Chat Input Area — standalone: flex footer (no global shell); Explore TV: docked inside TV column */}
          <div
            className={
              emb
                ? 'relative z-10 mt-auto flex-shrink-0 border-t border-[#2DE2C5]/35 bg-[#05080a]/98 backdrop-blur-md'
                : 'relative z-10 mt-auto flex-shrink-0 border-t border-slate-200 bg-white'
            }
            style={
              emb
                ? {
                    flexShrink: 0,
                    flex: '0 0 auto',
                    position: 'relative',
                    width: '100%',
                    boxShadow: 'inset 0 1px 0 rgba(45,226,197,0.12), 0 -12px 40px -18px rgba(45,226,197,0.25)'
                  }
                : {
                    background: 'white',
                    flexShrink: 0,
                    flex: '0 0 auto',
                    position: 'relative',
                    width: '100%'
                  }
            }
          >
            {/* Inner container to match chat messages width */}
            <div className={emb ? 'mx-auto w-full max-w-full px-3 pb-2' : 'mx-auto w-full max-w-2xl px-4'}>
            {/* Location Prompt - Show when location unavailable */}
            {showLocationPrompt && syncLocation && (
              <div className="pt-2 pb-1" data-testid="location-prompt">
                <div
                  className={
                    emb
                      ? 'flex items-start gap-2 rounded-lg border border-amber-500/35 bg-amber-950/40 p-2 sm:p-3'
                      : 'flex items-start gap-2 rounded-lg border border-amber-200 bg-amber-50 p-2 sm:p-3'
                  }
                >
                  <span className={emb ? 'text-sm text-amber-300 sm:text-base' : 'text-sm text-amber-600 sm:text-base'}>
                    📍
                  </span>
                  <div className="flex-1">
                    <p
                      className={
                        emb
                          ? 'mb-1 text-xs font-medium text-amber-100 sm:text-sm'
                          : 'mb-1 text-xs font-medium text-amber-800 sm:text-sm'
                      }
                    >
                      Location permission needed
                    </p>
                    <p className={emb ? 'text-xs text-amber-200/90' : 'text-xs text-amber-700'}>
                      Please allow location access to get personalized recommendations.
                    </p>
                    <button
                      onClick={async () => {
                        try {
                          const location = await getCurrentLocation();
                          setShowLocationPrompt(false);
                        } catch (err) {
                          if (window.uiLogger) {
                            window.uiLogger.uiWarn('Location request failed:', err.message || err);
                          }
                        }
                      }}
                      className={
                        emb
                          ? 'mt-2 text-xs text-amber-200 underline hover:text-amber-100 sm:text-sm'
                          : 'mt-2 text-xs text-amber-800 underline hover:text-amber-900 sm:text-sm'
                      }
                    >
                      Request location
                    </button>
                  </div>
                  <button
                    onClick={() => setShowLocationPrompt(false)}
                    className={
                      emb ? 'text-sm text-amber-300 hover:text-amber-100' : 'text-amber-600 hover:text-amber-800 text-sm'
                    }
                    aria-label="Dismiss location prompt"
                  >
                    ×
                  </button>
                </div>
              </div>
            )}
            
            {/* ✅ STEP 16: Sync Location Toggle - Above input box */}
            <div className="flex items-center justify-between pb-1 pt-2" style={{ paddingTop: '8px', paddingBottom: '10px' }}>
              <div className="flex items-center gap-2">
                <span className={emb ? 'text-xs text-[#2DE2C5]/90 sm:text-sm' : 'text-xs text-slate-600 sm:text-sm'}>
                  📍
                </span>
                <label
                  className={
                    emb
                      ? 'cursor-pointer text-xs font-medium text-[#2DE2C5]/90 sm:text-sm'
                      : 'cursor-pointer text-xs font-medium text-slate-700 sm:text-sm'
                  }
                  htmlFor="sync-location-toggle"
                >
                  Use my current location
                </label>
              </div>
              <label className="relative inline-flex cursor-pointer items-center">
                <input
                  type="checkbox"
                  id="sync-location-toggle"
                  checked={syncLocation}
                  onChange={(e) => {
                    setSyncLocation(e.target.checked);
                    if (window.uiLogger) {
                      window.uiLogger.uiLog(`📍 [ASK-SYLVAN] Location sync ${e.target.checked ? 'enabled' : 'disabled'}`);
                    }
                    // If enabling, fetch location immediately
                    if (e.target.checked) {
                      getCurrentLocation().catch(err => {
                        if (window.uiLogger) {
                          window.uiLogger.uiWarn('Failed to get location:', err.message || err);
                        }
                        // Show prompt if location fails
                        setShowLocationPrompt(true);
                      });
                    } else {
                      setShowLocationPrompt(false); // Hide prompt if sync disabled
                    }
                  }}
                  className="sr-only peer"
                />
                <div
                  className={
                    emb
                      ? 'relative h-6 w-11 rounded-full border border-[#2DE2C5]/35 bg-slate-700 after:absolute after:left-[2px] after:top-[2px] after:h-5 after:w-5 after:rounded-full after:border after:bg-white after:transition-all after:content-[""] peer-checked:bg-[#0d9488] peer-checked:after:translate-x-full peer-focus:outline-none peer-focus:ring-2 peer-focus:ring-[#2DE2C5]/40'
                      : 'relative h-6 w-11 rounded-full bg-[#CBD5E1] after:absolute after:left-[2px] after:top-[2px] after:h-5 after:w-5 after:rounded-full after:border after:border-[#CBD5E1] after:bg-white after:transition-all after:content-[""] peer-checked:bg-[#0F766E] peer-checked:after:translate-x-full peer-focus:outline-none peer-focus:ring-2 peer-focus:ring-[#0F766E]/30'
                  }
                />
              </label>
            </div>
            
            {/* ✅ STEP 16: Input row container */}
            <div className="py-2 sm:py-3" style={{ paddingTop: '0px' }}>
              <div className="flex gap-2 items-start">
                <div className="flex-1 relative">
                  <textarea
                    ref={inputRef}
                    data-testid="ask-sylvan-input"
                    value={inputText}
                    onChange={(e) => {
                      const text = e.target.value;
                      if (text.length <= 240) {
                        setInputText(text);
                      }
                    }}
                    onKeyPress={handleKeyPress}
                    placeholder="Ask Sylvan"
                    rows={1}
                    maxLength={240}
                    disabled={sending}
                    aria-busy={sending}
                    className={
                      emb
                        ? 'sf-ask-input w-full resize-none rounded-xl border border-[#2DE2C5]/35 bg-[#0a0c10] text-[15px] text-slate-100 placeholder-slate-500 focus:outline-none focus:ring-2 focus:ring-[#2DE2C5]/50 disabled:cursor-not-allowed disabled:opacity-70'
                        : 'sf-ask-input w-full text-base border focus:outline-none focus:ring-2 focus:ring-teal-500 resize-none placeholder-slate-400 disabled:opacity-70 disabled:cursor-not-allowed'
                    }
                    style={
                      emb
                        ? {
                            fontSize: '16px',
                            height: '44px',
                            minHeight: '44px',
                            padding: '12px 14px',
                            lineHeight: '1.2',
                            borderRadius: '14px',
                            boxSizing: 'border-box',
                            boxShadow:
                              'inset 0 0 0 1px rgba(45,226,197,0.08), 0 0 28px -12px rgba(45,226,197,0.35)'
                          }
                        : {
                            fontSize: '16px',
                            height: '44px',
                            minHeight: '44px',
                            padding: '12px 14px',
                            lineHeight: '1.2',
                            borderRadius: '14px',
                            boxSizing: 'border-box',
                            background: '#FFFFFF',
                            border: '1px solid #CBD5E1',
                            color: '#0F172A'
                          }
                    }
                  />
                  {/* ✅ STEP 14: Character Counter - Only show if >= 200 */}
                  {inputText.length >= 200 && (
                    <div
                      className={emb ? 'pointer-events-none absolute right-3 top-1/2 -translate-y-1/2 text-[#2DE2C5]/60 sm:right-4' : 'pointer-events-none absolute right-3 top-1/2 -translate-y-1/2 sm:right-4'}
                      style={{ fontSize: '12px', opacity: emb ? 1 : 0.6 }}
                    >
                      {inputText.length}/240
                    </div>
                  )}
                </div>
                {/* ✅ STEP 13: Send Button - 44x44 */}
                <button
                  onClick={sendMessage}
                  disabled={!inputText.trim() || sending}
                  className={
                    emb
                      ? 'flex min-h-[44px] min-w-[44px] shrink-0 items-center justify-center rounded-xl border border-[#2DE2C5]/40 shadow-[0_0_24px_-8px_rgba(45,226,197,0.55)] transition disabled:cursor-not-allowed'
                      : 'flex items-center justify-center transition-colors disabled:cursor-not-allowed'
                  }
                  style={
                    emb
                      ? {
                          width: '44px',
                          height: '44px',
                          background: 'linear-gradient(165deg, #2dd4bf 0%, #0d9488 45%, #0f766e 100%)',
                          color: '#fff',
                          boxShadow: '0 0 22px -6px rgba(45,226,197,0.55)'
                        }
                      : {
                          width: '44px',
                          height: '44px',
                          borderRadius: '12px',
                          background: '#0F766E',
                          color: '#fff'
                        }
                  }
                  onMouseEnter={(e) => {
                    if (!e.currentTarget.disabled) {
                      e.currentTarget.style.background = emb
                        ? 'linear-gradient(165deg, #5eead4 0%, #14b8a6 50%, #0f766e 100%)'
                        : '#115E59';
                    }
                  }}
                  onMouseLeave={(e) => {
                    if (!e.currentTarget.disabled) {
                      e.currentTarget.style.background = emb
                        ? 'linear-gradient(165deg, #2dd4bf 0%, #0d9488 45%, #0f766e 100%)'
                        : '#0F766E';
                    }
                  }}
                  aria-label="Send message"
                >
                  {sending ? (
                    <svg className="w-5 h-5 animate-spin" fill="none" viewBox="0 0 24 24">
                      <circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4"></circle>
                      <path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
                    </svg>
                  ) : (
                    <svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
                      <line x1="22" y1="2" x2="11" y2="13"></line>
                      <polygon points="22 2 15 22 11 13 2 9 22 2"></polygon>
                    </svg>
                  )}
                </button>
              </div>
            </div>
            </div>
          </div>
        </div>
        {!emb && window.InternalShellTwinNav
          ? React.createElement(window.InternalShellTwinNav, {
              variant: 'explore',
              leftLabel: 'Power Up',
              rightLabel: 'NuAsk'
            })
          : null}
      </div>
    );
  }

  // Export component
  if (typeof window !== 'undefined') {
    window.AskSylvanPage = AskSylvanPage;
  }
})();

