'use client' import { useState, useEffect, useRef, useCallback } from 'react' import { useTranslations, useLocale } from 'next-intl' import { useAuth } from '@/hooks/use-auth' import { useRouter, useSearchParams } from 'next/navigation' import { Box, Container, Typography, TextField, Button, Paper, Card, CardContent, Chip, List, ListItem, ListItemButton, ListItemText, IconButton, Tooltip, FormControl, InputLabel, Select, MenuItem, Checkbox, FormControlLabel, Collapse, Divider, Alert, Skeleton, Badge, ButtonGroup, InputAdornment, Dialog, DialogTitle, DialogContent, DialogActions, Fab, useTheme, useMediaQuery } from '@mui/material' import { Search, FilterList, History, Bookmark, BookmarkBorder, Share, Launch, ExpandMore, ExpandLess, Clear, TuneRounded, MenuBook, Article, ContentCopy, KeyboardArrowUp, AutoAwesome, SearchOff } from '@mui/icons-material' interface SearchResult { id: string verseId: string book: string bookId: string bookKey?: string chapter: number verse: number text: string relevance: number context?: { before?: string after?: string } } interface SearchFilters { testament: 'all' | 'old' | 'new' bookKeys: string[] searchType: 'phrase' | 'words' | 'exact' version: string showContext: boolean sortBy: 'relevance' | 'book' | 'reference' } interface BookOption { id: string name: string bookKey: string orderNum: number testament: string } interface SearchSuggestion { text: string type: 'history' | 'popular' | 'autocomplete' count?: number } export default function SearchPage() { const theme = useTheme() const isMobile = useMediaQuery(theme.breakpoints.down('md')) const t = useTranslations('pages.search') const locale = useLocale() const router = useRouter() const searchParams = useSearchParams() const { user } = useAuth() // Core search state const [searchQuery, setSearchQuery] = useState('') const [results, setResults] = useState([]) const [loading, setLoading] = useState(false) const [totalResults, setTotalResults] = useState(0) const [currentPage, setCurrentPage] = useState(1) const [shouldSearchFromUrl, setShouldSearchFromUrl] = useState(false) // UI state const [filtersOpen, setFiltersOpen] = useState(false) const [showSuggestions, setShowSuggestions] = useState(false) const [suggestions, setSuggestions] = useState([]) const [searchHistory, setSearchHistory] = useState([]) const [savedSearches, setSavedSearches] = useState([]) // Search configuration const [filters, setFilters] = useState({ testament: 'all', bookKeys: [], searchType: 'words', version: '', showContext: true, sortBy: 'relevance' }) // Data const [books, setBooks] = useState([]) const [versions, setVersions] = useState>([]) const [bookmarks, setBookmarks] = useState<{[key: string]: boolean}>({}) // Refs const searchInputRef = useRef(null) const resultsRef = useRef(null) const oldTestamentBooks = books.filter(b => b.orderNum <= 39) const newTestamentBooks = books.filter(b => b.orderNum > 39) const popularSearches = [ { text: t('popular.items.0'), type: 'popular' as const, count: 1250 }, { text: t('popular.items.1'), type: 'popular' as const, count: 980 }, { text: t('popular.items.2'), type: 'popular' as const, count: 875 }, { text: t('popular.items.3'), type: 'popular' as const, count: 720 }, { text: t('popular.items.4'), type: 'popular' as const, count: 680 }, { text: t('popular.items.5'), type: 'popular' as const, count: 650 }, { text: t('popular.items.6'), type: 'popular' as const, count: 580 }, { text: t('popular.items.7'), type: 'popular' as const, count: 520 }, { text: t('popular.items.8'), type: 'popular' as const, count: 480 }, { text: t('popular.items.9'), type: 'popular' as const, count: 420 } ] // Load search history and saved searches useEffect(() => { if (!user) return const userKey = `searchHistory_${user.id}` const history = localStorage.getItem(userKey) const saved = localStorage.getItem('savedSearches') if (history) { setSearchHistory(JSON.parse(history)) } if (saved) { setSavedSearches(JSON.parse(saved)) } }, [user]) // Load search parameters from URL (run only once on mount) useEffect(() => { const urlQuery = searchParams.get('q') const urlTestament = searchParams.get('testament') const urlSearchType = searchParams.get('type') const urlBooks = searchParams.get('books') if (urlQuery) { setSearchQuery(urlQuery) setShouldSearchFromUrl(true) } if (urlTestament || urlSearchType || urlBooks) { setFilters(prev => ({ ...prev, ...(urlTestament && { testament: urlTestament as any }), ...(urlSearchType && { searchType: urlSearchType as any }), ...(urlBooks && { bookKeys: urlBooks.split(',').filter(Boolean) }) })) } }, []) // Empty dependency array to run only once // Load versions and books useEffect(() => { const loadVersions = async () => { try { const response = await fetch(`/api/bible/versions?locale=${locale}`) const data = await response.json() const versionList: Array<{ id: string; name: string; abbreviation: string; isDefault: boolean }> = data.versions || [] setVersions(versionList) const defaultVersion = versionList.find(v => v.isDefault) || versionList[0] if (defaultVersion) { setFilters(prev => ({ ...prev, version: defaultVersion.abbreviation })) } } catch (error) { console.error('Failed to load versions:', error) } } loadVersions() }, [locale]) useEffect(() => { const loadBooks = async () => { if (!filters.version) return try { const response = await fetch(`/api/bible/books?locale=${locale}&version=${filters.version}`) const data = await response.json() const bookList = (data.books || []).map((b: any) => ({ id: b.id, name: b.name, bookKey: b.bookKey, orderNum: b.orderNum, testament: b.testament })) setBooks(bookList) } catch (error) { console.error('Failed to load books:', error) } } loadBooks() }, [locale, filters.version]) // Update suggestions based on search query useEffect(() => { if (!searchQuery.trim()) { setSuggestions([]) return } const historySuggestions = searchHistory .filter(h => h.toLowerCase().includes(searchQuery.toLowerCase())) .slice(0, 3) .map(text => ({ text, type: 'history' as const })) const popularSuggestions = popularSearches .filter(p => p.text.toLowerCase().includes(searchQuery.toLowerCase())) .slice(0, 3) setSuggestions([...historySuggestions, ...popularSuggestions]) }, [searchQuery, searchHistory]) const handleSearch = useCallback(async (query?: string, page = 1) => { const searchTerm = query || searchQuery if (!searchTerm.trim()) return setLoading(true) setCurrentPage(page) // Update search history (user-specific) if (user) { const userKey = `searchHistory_${user.id}` const newHistory = [searchTerm, ...searchHistory.filter(s => s !== searchTerm)].slice(0, 20) setSearchHistory(newHistory) localStorage.setItem(userKey, JSON.stringify(newHistory)) } try { const params = new URLSearchParams({ q: searchTerm, page: page.toString(), limit: '20', testament: filters.testament, searchType: filters.searchType, showContext: filters.showContext.toString(), sortBy: filters.sortBy, locale, version: filters.version, bookKeys: filters.bookKeys.join(',') }) const response = await fetch(`/api/search/verses?${params}`) if (!response.ok) { throw new Error('Search failed') } const data = await response.json() setResults(data.results || []) setTotalResults(data.total || 0) // Update URL with search parameters for sharing and tracking const urlParams = new URLSearchParams({ q: searchTerm, ...(filters.testament !== 'all' && { testament: filters.testament }), ...(filters.searchType !== 'words' && { type: filters.searchType }), ...(filters.bookKeys.length > 0 && { books: filters.bookKeys.join(',') }) }) const newUrl = `${window.location.pathname}?${urlParams.toString()}` window.history.replaceState({}, '', newUrl) // Scroll to results on mobile if (isMobile && resultsRef.current) { resultsRef.current.scrollIntoView({ behavior: 'smooth' }) } } catch (error) { console.error('Search error:', error) // Show demo results for development setResults([ { id: '1', verseId: 'verse-1', book: 'Ioan', bookId: 'john', chapter: 3, verse: 16, text: 'Fiindcă atât de mult a iubit Dumnezeu lumea, că a dat pe singurul Său Fiu, pentru ca oricine crede în El să nu piară, ci să aibă viața veșnică.', relevance: 0.95, context: { before: 'Și nimeni nu s-a suit în cer, afară de cel ce s-a coborât din cer, adică Fiul omului care este în cer.', after: 'Căci Dumnezeu nu a trimis pe Fiul Său în lume ca să judece lumea, ci pentru ca lumea să fie mântuită prin El.' } }, { id: '2', verseId: 'verse-2', book: '1 Corinteni', bookId: '1-corinthians', chapter: 13, verse: 4, text: 'Dragostea este îndelung răbdătoare, dragostea este binevoitoare; dragostea nu pizmuiește, dragostea nu se laudă, nu se îngâmfă;', relevance: 0.89, context: { before: 'De aș vorbi în limbile oamenilor și ale îngerilor, dar să n-am dragoste, am ajuns aramă sunătoare sau chimval zăngănitor.', after: 'nu lucrează cu necuviință, nu caută ale sale, nu se mânie, nu ține seama de rău;' } } ]) setTotalResults(2) } finally { setLoading(false) setShowSuggestions(false) } }, [searchQuery, filters, searchHistory, locale, isMobile]) // Trigger search from URL after handleSearch is defined useEffect(() => { if (shouldSearchFromUrl && searchQuery) { handleSearch(searchQuery, 1) setShouldSearchFromUrl(false) } }, [shouldSearchFromUrl, searchQuery, handleSearch]) const handleVerseBookmark = useCallback(async (result: SearchResult) => { if (!user) return const token = localStorage.getItem('authToken') if (!token) return try { const isBookmarked = bookmarks[result.verseId] if (isBookmarked) { const response = await fetch(`/api/bookmarks/verse?verseId=${result.verseId}&locale=${locale}`, { method: 'DELETE', headers: { 'Authorization': `Bearer ${token}` } }) if (response.ok) { setBookmarks(prev => { const updated = { ...prev } delete updated[result.verseId] return updated }) } } else { const response = await fetch(`/api/bookmarks/verse?locale=${locale}`, { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${token}` }, body: JSON.stringify({ verseId: result.verseId }) }) if (response.ok) { setBookmarks(prev => ({ ...prev, [result.verseId]: true })) } } } catch (error) { console.error('Bookmark error:', error) } }, [user, bookmarks, locale]) const handleCopyVerse = useCallback((result: SearchResult) => { const text = `${result.book} ${result.chapter}:${result.verse} - ${result.text}` navigator.clipboard.writeText(text) }, []) const handleNavigateToVerse = useCallback((result: SearchResult) => { const bookIdentifier = result.bookId || result.bookKey || result.book router.push(`/${locale}/bible?book=${bookIdentifier}&chapter=${result.chapter}&verse=${result.verse}`) }, [router, locale]) const clearFilters = useCallback(() => { setFilters(prev => ({ ...prev, testament: 'all', bookKeys: [], searchType: 'words' })) }, []) const highlightSearchTerm = useCallback((text: string, query: string) => { if (!query.trim()) return text const words = query.trim().split(/\s+/) let highlightedText = text words.forEach(word => { const regex = new RegExp(`(${word.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')})`, 'gi') highlightedText = highlightedText.replace(regex, '$1') }) return }, []) const activeFiltersCount = (filters.testament !== 'all' ? 1 : 0) + filters.bookKeys.length + (filters.searchType !== 'words' ? 1 : 0) return ( {/* Header */} {t('title')} {t('subtitle')} {/* Search Bar */} { setSearchQuery(e.target.value) setShowSuggestions(true) }} onFocus={() => setShowSuggestions(true)} onKeyPress={(e) => e.key === 'Enter' && handleSearch()} InputProps={{ startAdornment: ( ), endAdornment: searchQuery && ( { setSearchQuery('') setShowSuggestions(false) }} > ), sx: { fontSize: '1.1rem', '& .MuiOutlinedInput-root': { borderRadius: 2 } } }} /> {/* Search Suggestions */} 0}> {suggestions.map((suggestion, index) => ( { setSearchQuery(suggestion.text) handleSearch(suggestion.text) }} > {suggestion.type === 'history' && } {suggestion.type === 'popular' && } {suggestion.text} {suggestion.count && ( )} } /> ))} {/* Quick Filters */} {activeFiltersCount > 0 && ( )} {/* Advanced Filters */} {/* First row of filters */} {t('filters.version')} Sort by setFilters(prev => ({ ...prev, showContext: e.target.checked }))} /> } label="Show context" /> {/* Popular searches */} {t('popular.title')} {popularSearches.slice(0, 10).map((search, index) => ( { setSearchQuery(search.text) handleSearch(search.text) setFiltersOpen(false) }} /> ))} {/* Book selection */} {t('filters.specificBooks')} {books.map(book => ( { const exists = filters.bookKeys.includes(book.bookKey) const newBookKeys = exists ? filters.bookKeys.filter(k => k !== book.bookKey) : [...filters.bookKeys, book.bookKey] setFilters(prev => ({ ...prev, bookKeys: newBookKeys })) }} color={filters.bookKeys.includes(book.bookKey) ? 'primary' : 'default'} /> ))} {/* Sidebar */} {/* Search History */} {searchHistory.length > 0 && ( {t('history.title')} {searchHistory.slice(0, 8).map((query, index) => ( { setSearchQuery(query) handleSearch(query) }} /> ))} )} {/* Main Results */} {/* Results Header */} {(results.length > 0 || loading) && ( {loading ? t('searching') : t('results', { count: totalResults })} {results.length > 0 && ( Page {currentPage} )} )} {/* Loading State */} {loading && ( {Array.from({ length: 5 }).map((_, index) => ( ))} )} {/* Search Results */} {results.length > 0 && !loading && ( {results.map((result) => ( handleNavigateToVerse(result)} > {result.book} {result.chapter}:{result.verse} {user && ( handleVerseBookmark(result)} color={bookmarks[result.verseId] ? 'warning' : 'default'} > {bookmarks[result.verseId] ? : } )} handleCopyVerse(result)} > handleNavigateToVerse(result)} > {filters.showContext && result.context?.before && ( ...{result.context.before} )} {highlightSearchTerm(result.text, searchQuery)} {filters.showContext && result.context?.after && ( {result.context.after}... )} ))} {/* Load More / Pagination */} {totalResults > results.length && ( )} )} {/* No Results */} {!loading && searchQuery && results.length === 0 && ( {t('noResults.title')} {t('noResults.description')} )} {/* Empty State */} {!searchQuery && !loading && ( {t('empty.title')} {t('empty.description')} {t('filters.title')} → {t('popular.title')} )} {/* Scroll to Top */} {results.length > 5 && ( window.scrollTo({ top: 0, behavior: 'smooth' })} sx={{ position: 'fixed', bottom: 16, right: 16 }} > )} ) }