import React, { useState, useEffect, useRef } from 'react'; import { Search, Plus, Loader2, X, Globe, Briefcase, Info, TrendingUp, ShieldCheck, PlayCircle } from 'lucide-react'; import { motion, AnimatePresence } from 'framer-motion'; import { ChatGoogleGenerativeAI } from '@langchain/google-genai'; import { HumanMessage, SystemMessage } from '@langchain/core/messages'; export default function StockSearch({ isOpen, onClose, onAddStock, currentPortfolio, totalValue }) { const [query, setQuery] = useState(''); const [suggestions, setSuggestions] = useState([]); const [loading, setLoading] = useState(false); const [selectedStock, setSelectedStock] = useState(null); const [shares, setShares] = useState(1); const [impactAnalysis, setImpactAnalysis] = useState(''); const [isAnalyzing, setIsAnalyzing] = useState(false); const [performanceData, setPerformanceData] = useState({ threeYearReturn: 0 }); const [error, setError] = useState(''); const searchRef = useRef(null); // Debounced search for suggestions useEffect(() => { const timer = setTimeout(async () => { if (query.length > 1) { setLoading(true); try { const url = `/api/yahoo/v1/finance/search?q=${query}&newsCount=0`; const response = await fetch(url); const data = await response.json(); if (data.quotes) { // Filter for US Stocks and Mutual Funds only const usExchanges = ['NYSE', 'NASDAQ', 'AMEX', 'BATS', 'ARCA', 'OTCQB', 'OTCQX']; const filtered = data.quotes .filter(q => { const isUS = usExchanges.some(ex => q.exchDisp?.includes(ex)) || !q.symbol.includes('.'); const isTargetType = q.quoteType === 'EQUITY' || q.quoteType === 'MUTUALFUND'; return q.symbol && isUS && isTargetType; }) .slice(0, 6); setSuggestions(filtered); } } catch (err) { console.error("Autocomplete error:", err); } finally { setLoading(false); } } else { setSuggestions([]); } }, 300); return () => clearTimeout(timer); }, [query]); // Reset states when selectedStock is cleared useEffect(() => { if (!selectedStock) { setImpactAnalysis(''); setShares(1); } }, [selectedStock]); // Calculate 3-year performance whenever selection changes useEffect(() => { if (selectedStock) { // Generate a stable mock performance based on ticker hash const ticker = selectedStock.symbol; let hash = 0; for (let i = 0; i < ticker.length; i++) { hash = ((hash << 5) - hash) + ticker.charCodeAt(i); hash |= 0; } // Generate return between -20% and +120% const returnVal = (hash % 140) - 20; setPerformanceData({ threeYearReturn: returnVal }); } }, [selectedStock]); const analyzeImpact = async (stock, quantity) => { console.log("Starting analyzeImpact", { stock: stock.symbol, quantity }); const apiKey = import.meta.env.VITE_GEMINI_API_KEY; if (!apiKey || apiKey.length < 10) { console.warn("API Key missing or too short, using local fallback"); setImpactAnalysis(`Adding ${quantity} shares of ${stock.symbol} will broaden your market exposure and shift your portfolio beta towards a more dynamic profile.`); return; } setIsAnalyzing(true); setImpactAnalysis(''); // Clear previous try { console.log("Invoking Gemini for impact analysis..."); const llm = new ChatGoogleGenerativeAI({ apiKey, modelName: 'gemini-1.5-flash', maxRetries: 1 }); const portfolioTickers = currentPortfolio.allocation.map(a => a.ticker).join(', ') || 'none'; const response = await llm.invoke([ new SystemMessage(`You are a quantitative advisor. Analyze the impact of adding ${quantity} shares of ${stock.symbol} to a portfolio of ${portfolioTickers}. Output 1 short, specific sentence.`), new HumanMessage(`Impact of ${quantity} shares of ${stock.symbol}.`) ]); console.log("AI Response received:", response.content); setImpactAnalysis(response.content); } catch (err) { console.error("Impact Analysis API Error:", err); // Immediate fallback so the user doesn't see a blank screen const fallbackText = `This position in ${stock.symbol} provides new exposure that complements your existing holdings in ${currentPortfolio.allocation[0]?.ticker || 'diversified assets'}.`; setImpactAnalysis(fallbackText); } finally { setIsAnalyzing(false); console.log("Analysis cycle complete"); } }; const handleSelect = (quote) => { setSelectedStock(quote); // Initial analysis triggered by useEffect }; const handleConfirm = () => { onAddStock({ ticker: selectedStock.symbol, name: selectedStock.shortname || selectedStock.longname || selectedStock.symbol, type: selectedStock.quoteType === 'MUTUALFUND' ? 'mf' : 'stock', value: 5, // Base weighting, dashboard handles rebalance color: `#${Math.floor(Math.random()*16777215).toString(16)}`, shares: Number(shares), dateBought: new Date().toISOString().split('T')[0] }); setQuery(''); setSelectedStock(null); setSuggestions([]); onClose(); }; if (!isOpen) return null; return (
{selectedStock.shortname || selectedStock.longname}
Trailing 3-Year Return
= 0 ? 'text-green-400' : 'text-red-400'}`}> {performanceData.threeYearReturn >= 0 ? '+' : ''}{performanceData.threeYearReturn.toFixed(2)}%
Historical market performance
{impactAnalysis}
) : ( )}Try searching for "Apple", "NVDA", or "Bitcoin"