import React, { useState, useEffect } from 'react'; import { RefreshCw, PackagePlus, Loader2, CheckCircle, XCircle, Trash2 } from 'lucide-react'; import { searchCards, getUserCollection, addCardToCollection } from '../services/api'; import { Card } from '../types'; import { useAuth } from '../contexts/AuthContext'; import MagicCard from './MagicCard'; import { getManaIconPath } from './ManaCost'; const CardSearch = () => { const { user } = useAuth(); const [cardName, setCardName] = useState(''); const [text, setText] = useState(''); const [rulesText, setRulesText] = useState(''); const [typeLine, setTypeLine] = useState(''); const [typeMatch, setTypeMatch] = useState('partial'); const [typeInclude, setTypeInclude] = useState(true); const [colors, setColors] = useState({ W: false, U: false, B: false, R: false, G: false, C: false }); const [colorMode, setColorMode] = useState('exactly'); const [commanderColors, setCommanderColors] = useState({ W: false, U: false, B: false, R: false, G: false, C: false }); const [manaCost, setManaCost] = useState({ W: 0, U: 0, B: 0, R: 0, G: 0, C: 0 }); const [manaValue, setManaValue] = useState(''); const [manaValueComparison, setManaValueComparison] = useState('='); const [games, setGames] = useState({ paper: false, arena: false, mtgo: false }); const [format, setFormat] = useState(''); const [formatStatus, setFormatStatus] = useState(''); const [set, setSet] = useState(''); const [block, setBlock] = useState(''); const [rarity, setRarity] = useState({ common: false, uncommon: false, rare: false, mythic: false }); const [criteria, setCriteria] = useState(''); const [criteriaMatch, setCriteriaMatch] = useState('partial'); const [criteriaInclude, setCriteriaInclude] = useState(true); const [price, setPrice] = useState(''); const [currency, setCurrency] = useState('usd'); const [priceComparison, setPriceComparison] = useState('='); const [artist, setArtist] = useState(''); const [flavorText, setFlavorText] = useState(''); const [loreFinder, setLoreFinder] = useState(''); const [language, setLanguage] = useState('en'); const [displayImages, setDisplayImages] = useState(false); const [order, setOrder] = useState('name'); const [showAllPrints, setShowAllPrints] = useState(false); const [includeExtras, setIncludeExtras] = useState(false); const [searchResults, setSearchResults] = useState([]); const [loading, setLoading] = useState(false); const [error, setError] = useState(null); // Collection state const [userCollection, setUserCollection] = useState>(new Map()); const [addingCardId, setAddingCardId] = useState(null); const [cardFaceIndex, setCardFaceIndex] = useState>(new Map()); const [snackbar, setSnackbar] = useState<{ message: string; type: 'success' | 'error' } | null>(null); // Load user collection useEffect(() => { const loadUserCollection = async () => { if (!user) return; try { const collection = await getUserCollection(user.id); setUserCollection(collection); } catch (error) { console.error('Error loading user collection:', error); } }; loadUserCollection(); }, [user]); // Helper function to check if a card has an actual back face const isDoubleFaced = (card: Card) => { const backFaceLayouts = ['transform', 'modal_dfc', 'double_faced_token', 'reversible_card']; return card.card_faces && card.card_faces.length > 1 && backFaceLayouts.includes(card.layout); }; // Get current face index for a card const getCurrentFaceIndex = (cardId: string) => { return cardFaceIndex.get(cardId) || 0; }; // Toggle card face const toggleCardFace = (cardId: string, totalFaces: number) => { setCardFaceIndex(prev => { const newMap = new Map(prev); const currentIndex = prev.get(cardId) || 0; const nextIndex = (currentIndex + 1) % totalFaces; newMap.set(cardId, nextIndex); return newMap; }); }; // Get card image for current face const getCardImageUri = (card: Card, faceIndex: number = 0) => { if (isDoubleFaced(card) && card.card_faces) { return card.card_faces[faceIndex]?.image_uris?.normal || card.card_faces[faceIndex]?.image_uris?.small; } return card.image_uris?.normal || card.image_uris?.small || card.card_faces?.[0]?.image_uris?.normal; }; // Get card art crop for current face const getCardArtCrop = (card: Card, faceIndex: number = 0) => { if (isDoubleFaced(card) && card.card_faces) { return card.card_faces[faceIndex]?.image_uris?.art_crop || card.card_faces[faceIndex]?.image_uris?.normal; } return card.image_uris?.art_crop || card.image_uris?.normal || card.card_faces?.[0]?.image_uris?.art_crop; }; // Add card to collection const handleAddCardToCollection = async (cardId: string) => { if (!user) { setSnackbar({ message: 'Please log in to add cards to your collection', type: 'error' }); setTimeout(() => setSnackbar(null), 3000); return; } try { setAddingCardId(cardId); await addCardToCollection(user.id, cardId, 1); setUserCollection(prev => { const newMap = new Map(prev); const currentQty = newMap.get(cardId) || 0; newMap.set(cardId, currentQty + 1); return newMap; }); setSnackbar({ message: 'Card added to collection!', type: 'success' }); } catch (error) { console.error('Error adding card to collection:', error); setSnackbar({ message: 'Failed to add card to collection', type: 'error' }); } finally { setAddingCardId(null); setTimeout(() => setSnackbar(null), 3000); } }; const handleSearch = async (e: React.FormEvent) => { e.preventDefault(); setLoading(true); setError(null); let query = ''; if (cardName) query += `name:${cardName} `; if (text) query += `o:${text} `; if (rulesText) query += `o:"${rulesText.replace('~', cardName)}" `; if (typeLine) { const typeQuery = typeMatch === 'partial' ? typeLine : `"${typeLine}"`; query += `${typeInclude ? '' : '-'}t:${typeQuery} `; } if (Object.values(colors).some(Boolean)) { const activeColors = Object.keys(colors).filter((key) => colors[key as keyof typeof colors]).join(''); const colorQuery = colorMode === 'exactly' ? `c:${activeColors}` : `color<=${activeColors}`; query += `${colorQuery} `; } if (Object.values(commanderColors).some(Boolean)) { const activeColors = Object.keys(commanderColors).filter((key) => commanderColors[key as keyof typeof commanderColors]).join(''); query += `id:${activeColors} `; } const manaCostString = Object.entries(manaCost) .filter(([, count]) => count > 0) .map(([color, count]) => `{${color}}`.repeat(count)) .join(''); if (manaCostString) query += `m:${manaCostString} `; if (manaValue) query += `mv${manaValueComparison}${manaValue} `; if (Object.values(games).some(Boolean)) { const activeGames = Object.keys(games).filter((key) => games[key as keyof typeof games]).join(','); query += `game:${activeGames} `; } if (format) query += `f:${format} `; if (formatStatus) query += `${formatStatus}:${format} `; if (set) query += `e:${set} `; if (block) query += `b:${block} `; if (Object.values(rarity).some(Boolean)) { const activeRarities = Object.keys(rarity).filter((key) => rarity[key as keyof typeof rarity]).join(','); query += `r:${activeRarities} `; } if (criteria) { const criteriaQuery = criteriaMatch === 'partial' ? criteria : `"${criteria}"`; query += `${criteriaInclude ? '' : '-'}o:${criteriaQuery} `; } if (price) query += `${currency}${priceComparison}${price} `; if (artist) query += `a:${artist} `; if (flavorText) query += `ft:${flavorText} `; if (loreFinder) query += `${loreFinder} `; if (language) query += `lang:${language} `; if (displayImages) query += `display:grid `; if (order) query += `order:${order} `; if (showAllPrints) query += `unique:prints `; if (includeExtras) query += `include:extras `; try { const cards = await searchCards(query.trim()); setSearchResults(cards || []); } catch (err) { setError('Failed to fetch cards.'); console.error('Error fetching cards:', err); } finally { setLoading(false); } }; return (

Card Search

{/* Card Details */}
setCardName(e.target.value)} className="w-full px-4 py-2 bg-gray-800 border border-gray-700 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent text-white" placeholder="Card Name" /> setText(e.target.value)} className="w-full px-4 py-2 bg-gray-800 border border-gray-700 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent text-white" placeholder="Text" /> setRulesText(e.target.value)} className="w-full px-4 py-2 bg-gray-800 border border-gray-700 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent text-white" placeholder="Rules Text (~ for card name)" />
setTypeLine(e.target.value)} className="flex-1 px-4 py-2 bg-gray-800 border border-gray-700 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent text-white" placeholder="Type Line" />
{/* Colors */}

Card Colors

{Object.entries(colors).map(([color, active]) => ( ))}

Commander Colors

{Object.entries(commanderColors).map(([color, active]) => ( ))}
{/* Mana Cost */}
{Object.entries(manaCost).map(([color, count]) => { const iconPath = getManaIconPath(color); return (
{iconPath ? ( {color} ) : ( {color} )} setManaCost({ ...manaCost, [color]: parseInt(e.target.value) })} className="w-14 sm:w-16 px-2 py-2 bg-gray-800 border border-gray-700 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent text-white" min="0" />
); })}
{/* Stats */}
setManaValue(e.target.value)} className="flex-1 px-4 py-2 bg-gray-800 border border-gray-700 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent text-white" placeholder="Mana Value" />
{/* Games */}

Games

{['paper', 'arena', 'mtgo'].map((game) => ( ))}
{/* Formats */}
{/* Sets */}
setSet(e.target.value)} className="flex-1 px-4 py-2 bg-gray-800 border border-gray-700 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent text-white" placeholder="Set Code" /> setBlock(e.target.value)} className="flex-1 px-4 py-2 bg-gray-800 border border-gray-700 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent text-white" placeholder="Block Code" />
{/* Rarity */}

Rarity

{['common', 'uncommon', 'rare', 'mythic'].map((r) => ( ))}
{/* Criteria */}
setCriteria(e.target.value)} className="flex-1 px-4 py-2 bg-gray-800 border border-gray-700 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent text-white" placeholder="Criteria" />
{/* Prices */}
setPrice(e.target.value)} className="flex-1 px-4 py-2 bg-gray-800 border border-gray-700 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent text-white" placeholder="Price" />
{/* Additional Filters */}
setArtist(e.target.value)} className="w-full px-4 py-2 bg-gray-800 border border-gray-700 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent text-white" placeholder="Artist" /> setFlavorText(e.target.value)} className="w-full px-4 py-2 bg-gray-800 border border-gray-700 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent text-white" placeholder="Flavor Text" /> setLoreFinder(e.target.value)} className="w-full px-4 py-2 bg-gray-800 border border-gray-700 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent text-white" placeholder="Lore Finderâ„¢" />
{/* Preferences */}
{loading && (
)} {error && (
{error}
)} {searchResults && searchResults.length > 0 && ( <> {/* Mobile: Horizontal list layout */}
{searchResults.map((card) => { const currentFaceIndex = getCurrentFaceIndex(card.id); const isMultiFaced = isDoubleFaced(card); const inCollection = userCollection.get(card.id) || 0; const isAddingThisCard = addingCardId === card.id; const displayName = isMultiFaced && card.card_faces ? card.card_faces[currentFaceIndex]?.name || card.name : card.name; return (
{/* Card art crop */}
{displayName} {isMultiFaced && ( )}
{/* Info */}

{displayName}

{card.prices?.usd && ${card.prices.usd}} {inCollection > 0 && ( x{inCollection} )}
{/* Action button */}
); })}
{/* Desktop: Grid layout */}
{searchResults.map((card) => { const currentFaceIndex = getCurrentFaceIndex(card.id); const isMultiFaced = isDoubleFaced(card); const inCollection = userCollection.get(card.id) || 0; const isAddingThisCard = addingCardId === card.id; const displayName = isMultiFaced && card.card_faces ? card.card_faces[currentFaceIndex]?.name || card.name : card.name; return (
{getCardImageUri(card, currentFaceIndex) ? ( {displayName} ) : ( )} {isMultiFaced && ( )} {inCollection > 0 && ( x{inCollection} )}

{displayName}

{isMultiFaced && card.card_faces ? card.card_faces[currentFaceIndex]?.type_line || card.type_line : card.type_line}

{card.prices?.usd && (
${card.prices.usd}
)}
); })}
)}
{/* Snackbar */} {snackbar && (
{snackbar.type === 'success' ? ( ) : ( )} {snackbar.message}
)}
); }; export default CardSearch;