function ProductSearch()

in src/application/ui/src/App.js [47:811]


function ProductSearch() {
    const navigate = useNavigate();
    // Simplified search state - only track the active search query
    const [activeSearchQuery, setActiveSearchQuery] = useState(''); // For actual API requests
    const [showAIFilterSuggestions, setShowAIFilterSuggestions] = useState(false); // Control visibility of AI filter suggestions
    const [products, setProducts] = useState([]);
    const [loading, setLoading] = useState(false);
    const [error, setError] = useState(null);
    // State for the filters component - now a dynamic object
    const [selectedFilters, setSelectedFilters] = useState({});
    // Store filter configurations
    const [filterConfigs, setFilterConfigs] = useState([]);
    const [currentPage, setCurrentPage] = useState(1);
    const productsPerPage = 12;

    // Format a filter name for display (convert lens_color to Lens Color)
    const formatFilterName = (name) => {
        return name
            .split('_')
            .map(word => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase())
            .join(' ');
    };

    // Generate filter configurations based on product data
    const generateFilterConfigs = useCallback((products) => {
        if (!products || products.length === 0) {
            return [];
        }

        const configs = [];
        // Track filter IDs to prevent duplicates
        const addedFilterIds = new Set();

        const filterAdders = [
            // Categories filter
            () => {
                const allCategories = new Set();
                products.forEach(product => {
                    if (product.categories) {
                        product.categories.forEach(category => allCategories.add(category));
                    }
                });

                const categories = Array.from(allCategories);
                if (categories.length > 0) {
                    configs.push({
                        id: 'categories',
                        title: 'Department',
                        priority: 100, // High priority
                        options: categories.map(cat => ({ label: cat, value: cat })),
                        type: 'checkbox'
                    });
                }
            },

            // Brands filter
            () => {
                const allBrands = new Set();
                products.forEach(product => {
                    if (product.brands) {
                        product.brands.forEach(brand => allBrands.add(brand));
                    }
                    // Also check attributes for brand if top-level is missing
                    if (!product.brands && product.attributes) {
                        const brandAttr = product.attributes.find(attr => attr.key === 'brand');
                        if (brandAttr && brandAttr.value?.text) {
                            brandAttr.value.text.forEach(b => allBrands.add(b));
                        }
                    }
                });

                const brands = Array.from(allBrands);
                if (brands.length > 0) {
                    configs.push({
                        id: 'brands',
                        title: 'Brands',
                        priority: 90, // High priority
                        options: brands.map(brand => ({ label: brand, value: brand })),
                        type: 'checkbox'
                    });
                }
            },

            // Price ranges filter
            () => {
                const validPrices = products
                    .map(p => p.priceInfo?.price)
                    .filter(price => price !== null && price !== undefined && !isNaN(parseFloat(price)))
                    .map(price => parseFloat(price));

                if (validPrices.length < 2) return; // Need at least two prices to make ranges

                const minPrice = Math.min(...validPrices);
                const maxPrice = Math.max(...validPrices);

                if (minPrice === maxPrice) {
                    configs.push({
                        id: 'prices',
                        title: 'Price',
                        priority: 80, // High priority
                        options: [{ label: `$${minPrice.toFixed(2)}`, value: `${minPrice}-${minPrice}` }],
                        type: 'checkbox'
                    });
                    return;
                }

                const numRanges = 4; // Define number of ranges
                const step = (maxPrice - minPrice) / numRanges;
                const ranges = [];

                for (let i = 0; i < numRanges; i++) {
                    const rangeMin = minPrice + i * step;
                    const rangeMax = minPrice + (i + 1) * step;
                    // Ensure the last range includes the max price
                    const finalMax = (i === numRanges - 1) ? maxPrice : rangeMax;

                    ranges.push({
                        // Use Math.floor/ceil to avoid overlapping ranges due to floating point issues
                        label: `$${Math.floor(rangeMin).toFixed(2)} - $${Math.ceil(finalMax).toFixed(2)}`,
                        value: `${Math.floor(rangeMin)}-${Math.ceil(finalMax)}`
                    });
                }

                configs.push({
                    id: 'prices',
                    title: 'Price',
                    options: ranges,
                    type: 'checkbox'
                });
            },

            // Colors filter
            () => {
                const allColors = new Set();
                products.forEach(product => {
                    if (product.colorInfo?.colors) {
                        product.colorInfo.colors.forEach(color => allColors.add(color));
                    }
                    if (product.colorInfo?.colorFamilies) {
                        product.colorInfo.colorFamilies.forEach(color => allColors.add(color));
                    }
                });

                const colors = Array.from(allColors);
                if (colors.length > 0) {
                    configs.push({
                        id: 'colors',
                        title: 'Colors',
                        priority: 70, // Medium-high priority
                        options: colors.map(color => ({ label: color, value: color })),
                        type: 'checkbox'
                    });
                }
            },

            // Sizes filter
            () => {
                const allSizes = new Set();
                products.forEach(product => {
                    if (product.sizes) {
                        product.sizes.forEach(size => allSizes.add(size));
                    }
                });

                const sizes = Array.from(allSizes);
                if (sizes.length > 0) {
                    configs.push({
                        id: 'sizes',
                        title: 'Sizes',
                        priority: 60, // Medium priority
                        options: sizes.map(size => ({ label: size, value: size })),
                        type: 'checkbox'
                    });
                }
            },

            // Availability filter
            () => {
                const availabilityOptions = [
                    { label: 'In Stock', value: 'IN_STOCK' },
                    { label: 'Out of Stock', value: 'OUT_OF_STOCK' }
                ];

                // Only add if both statuses are present
                const hasInStock = products.some(p => p.availability === 'IN_STOCK');
                const hasOutOfStock = products.some(p => p.availability === 'OUT_OF_STOCK');

                if (hasInStock && hasOutOfStock) {
                    configs.push({
                        id: 'availability',
                        title: 'Availability',
                        priority: 50, // Medium priority
                        options: availabilityOptions,
                        type: 'checkbox'
                    });
                }
            },

            // Dynamic attributes filter
            () => {
                // Find common attribute keys that appear in multiple products
                const attributeKeys = new Map();

                products.forEach(product => {
                    if (product.attributes) {
                        product.attributes.forEach(attr => {
                            if (attr.key && attr.value) {
                                const count = attributeKeys.get(attr.key) || 0;
                                attributeKeys.set(attr.key, count + 1);
                            }
                        });
                    }
                });

                // For each common attribute, create a filter
                attributeKeys.forEach((count, key) => {
                    // Skip if it only appears in one product or is already used (like brand)
                    if (count < 2 || key === 'brand') return;

                    // Get all values for this attribute
                    const values = new Set();
                    products.forEach(product => {
                        if (product.attributes) {
                            const attr = product.attributes.find(a => a.key === key);
                            if (attr && attr.value?.text) {
                                attr.value.text.forEach(val => values.add(val));
                            }
                        }
                    });

                    if (values.size > 0) {
                        configs.push({
                            id: `attr_${key}`,
                            title: formatFilterName(key),
                            priority: 40, // Lower priority for dynamic attributes
                            options: Array.from(values).map(value => ({ label: value, value })),
                            type: 'checkbox'
                        });
                    }
                });
            }
        ];

        // Apply all filter adders
        filterAdders.forEach(adder => adder());

        // Remove any duplicate filters by ID 
        let uniqueConfigs = configs.filter(config => {
            if (addedFilterIds.has(config.id)) {
                return false;
            }
            addedFilterIds.add(config.id);
            return true;
        });

        // Prepare the final filter list
        const finalFilters = [];

        // 1. Ensure Department filter is included
        // Department is typically derived from categories
        const categoryFilter = uniqueConfigs.find(config => config.id === 'categories');
        if (categoryFilter) {
            // Rename to Department for better user understanding
            categoryFilter.title = 'Department';
            finalFilters.push(categoryFilter);
            // Remove from uniqueConfigs to avoid duplication
            uniqueConfigs = uniqueConfigs.filter(config => config.id !== 'categories');
        }

        // 2. First ensure Price filter is included (if available)
        const priceFilter = uniqueConfigs.find(config => config.id === 'prices');
        if (priceFilter) {
            finalFilters.push(priceFilter);
            // Remove from uniqueConfigs to avoid duplication
            uniqueConfigs = uniqueConfigs.filter(config => config.id !== 'prices');
        }

        // 3. Sort remaining filters by priority and add up
        const remainingSlots = 10 - finalFilters.length;
        if (remainingSlots > 0 && uniqueConfigs.length > 0) {
            const remainingFilters = uniqueConfigs
                .sort((a, b) => (b.priority || 0) - (a.priority || 0))
                .slice(0, remainingSlots);

            finalFilters.push(...remainingFilters);
        }

        return finalFilters;
    }, []);

    // Use memoized filter configurations
    useEffect(() => {
        const configs = generateFilterConfigs(products);
        setFilterConfigs(configs);
    }, [products, generateFilterConfigs]);

    // The ProductImage component has been moved to its own file for better performance

    const fetchProducts = useCallback(async () => {
        setLoading(true);
        setError(null);
        try {
            const response = await axios.post(`${API_URL}/search`, {
                query: activeSearchQuery, // Use activeSearchQuery instead of searchQuery
                limit: 300,
                min_score: 0.01,
                alpha: 0.0,
            });

            console.log("API Response:", JSON.stringify(response.data, null, 2));

            // Simple processing - just map products without modifying anything
            const processedResults = response.data.results || [];

            // Debug: Print the first product's image URL to console
            if (processedResults.length > 0 && processedResults[0].images && processedResults[0].images.length > 0) {
                console.log("First product image URL:", processedResults[0].images[0].uri);
            }

            console.log("Products to display:", processedResults.length);

            // Store only necessary product data (without images) in localStorage for details page
            const productsForStorage = processedResults.map(product => {
                // Create a copy of the product without the images array to reduce storage size
                const { images, ...productWithoutImages } = product;
                return {
                    ...productWithoutImages,
                    // Store just a reference to the first image URL if available
                    primaryImageUrl: images && images.length > 0 ? images[0].uri : null
                };
            });
            localStorage.setItem('searchResults', JSON.stringify(productsForStorage));

            // Set products and reset to first page
            setProducts(processedResults);
            setCurrentPage(1);

            // Show AI filter suggestions if we have products and a search query
            setShowAIFilterSuggestions(processedResults.length > 0 && activeSearchQuery.trim() !== '');
        } catch (error) {
            setError(error);
            console.error("Search error:", error);
        } finally {
            setLoading(false);
        }
    }, [activeSearchQuery]); // Changed dependency from searchQuery to activeSearchQuery

    // Handler for filter changes from the Filters component - now works with dynamic filter IDs
    const handleFilterChange = useCallback((filterId, value, isChecked) => {
        setSelectedFilters(prevFilters => {
            const currentSelection = prevFilters[filterId] || [];
            let newSelection;
            if (isChecked) {
                newSelection = [...currentSelection, value];
            } else {
                newSelection = currentSelection.filter(item => item !== value);
            }
            return {
                ...prevFilters,
                [filterId]: newSelection,
            };
        });
        setCurrentPage(1); // Reset to first page when filters change
    }, []);

    // Dynamic filter logic with optimized memory usage
    const filteredProducts = useMemo(() => {
        if (!products.length || !Object.keys(selectedFilters).length) {
            return products;
        }

        // First identify which filter types are actually being used to avoid unnecessary processing
        const activeFilters = Object.entries(selectedFilters).filter(
            ([_, values]) => values && values.length > 0
        );

        // If no active filters, return all products
        if (activeFilters.length === 0) {
            return products;
        }

        // Create filter functions for each active filter type
        // This avoids recreating them for each product
        const filterFunctions = activeFilters.map(([filterId, selectedValues]) => {
            switch (filterId) {
                case 'categories':
                    return product =>
                        product.categories &&
                        product.categories.some(cat => selectedValues.includes(cat));

                case 'brands':
                    return product => {
                        // Check direct brands first
                        if (product.brands && product.brands.some(brand => selectedValues.includes(brand))) {
                            return true;
                        }
                        // Check attribute brands as fallback
                        if (product.attributes) {
                            const brandAttr = product.attributes.find(attr => attr.key === 'brand');
                            return brandAttr && brandAttr.value?.text &&
                                brandAttr.value.text.some(b => selectedValues.includes(b));
                        }
                        return false;
                    };

                case 'prices':
                    // Pre-parse the price ranges to avoid repeated parsing
                    const priceRanges = selectedValues.map(range => {
                        const [min, max] = range.split('-').map(parseFloat);
                        return { min, max };
                    });

                    return product => {
                        const price = product.priceInfo?.price ? parseFloat(product.priceInfo.price) : null;
                        if (price === null) return false;
                        return priceRanges.some(range => price >= range.min && price <= range.max);
                    };

                case 'colors':
                    return product => {
                        const colorInfo = product.colorInfo;
                        if (!colorInfo) return false;

                        // Check direct colors
                        if (colorInfo.colors && colorInfo.colors.some(color => selectedValues.includes(color))) {
                            return true;
                        }
                        // Check color families
                        return colorInfo.colorFamilies &&
                            colorInfo.colorFamilies.some(color => selectedValues.includes(color));
                    };

                case 'sizes':
                    return product =>
                        product.sizes &&
                        product.sizes.some(size => selectedValues.includes(size));

                case 'availability':
                    return product => selectedValues.includes(product.availability);

                default:
                    // Handle dynamic attribute filters (attr_*)
                    if (filterId.startsWith('attr_')) {
                        const attrKey = filterId.substring(5); // Remove 'attr_' prefix
                        return product => {
                            if (!product.attributes) return false;
                            const attr = product.attributes.find(a => a.key === attrKey);
                            return attr && attr.value?.text &&
                                attr.value.text.some(val => selectedValues.includes(val));
                        };
                    }

                    // Default behavior for unknown filter types
                    return () => true;
            }
        });

        // Apply all filter functions to each product
        return products.filter(product =>
            filterFunctions.every(filterFn => filterFn(product))
        );
    }, [products, selectedFilters]);

    // Handle search submission from SearchInput component - now resets all filters
    const handleSearch = useCallback((searchText) => {
        // Reset all filters when a new search is performed
        setSelectedFilters({});
        // Update active search query which triggers API call
        setActiveSearchQuery(searchText);
        // Reset AI filter suggestions when a new search is performed
        setShowAIFilterSuggestions(true);
    }, []);

    useEffect(() => {
        if (activeSearchQuery.trim()) {
            fetchProducts();
        }

        // Cleanup function to prevent memory leaks
        return () => {
            // Clear any references to large data structures
            setProducts([]);
            setFilterConfigs([]);
        };
    }, [fetchProducts, activeSearchQuery]);

    const indexOfLastProduct = currentPage * productsPerPage;
    const indexOfFirstProduct = indexOfLastProduct - productsPerPage;
    const currentProducts = useMemo(() => {
        return filteredProducts.slice(indexOfFirstProduct, indexOfLastProduct);
    }, [filteredProducts, indexOfFirstProduct, indexOfLastProduct]);

    const totalPages = Math.ceil(filteredProducts.length / productsPerPage);

    const handlePageChange = (newPage) => {
        setCurrentPage(newPage);
    };


    // Prevent search input changes from causing product grid re-renders
    // Only fetch products when activeSearchQuery changes (not during typing)

    // Stable handler for product clicks
    const handleProductClick = useCallback((productId) => {
        navigate(`/product/${productId}`);
    }, [navigate]);

    // Optimized product grid with on-demand image processing
    const ProductGrid = memo(({ products, onProductClick }) => {
        return (
            <Grid container spacing={3}>
                {products.map((product) => {
                    // Extract just the minimal image data needed for this specific product
                    // This is done inline instead of processing all products at once
                    const imageUrl = product.images &&
                        Array.isArray(product.images) &&
                        product.images.length > 0 &&
                        product.images[0].uri ?
                        product.images[0].uri :
                        null;

                    return (
                        <Grid item xs={12} sm={6} md={3} key={product.id}>
                            <Card
                                sx={{
                                    height: '100%',
                                    display: 'flex',
                                    flexDirection: 'column',
                                    cursor: 'pointer',
                                    '&:hover': {
                                        boxShadow: 6,
                                    }
                                }}
                                onClick={() => onProductClick(product.id)}
                            >
                                {/* Each product processes its own image data */}
                                <ProductImage
                                    imageUrl={imageUrl}
                                    productName={product.name}
                                />
                                <CardContent sx={{ flexGrow: 1 }}>
                                    <Typography gutterBottom variant="h6" component="div">
                                        {product.name}
                                    </Typography>
                                    <Typography variant="body2" color="text.secondary" gutterBottom>
                                        {product.title}
                                    </Typography>
                                    <Box sx={{ mt: 1, mb: 1 }}>
                                        {product.categories && product.categories.map(category => (
                                            <Chip
                                                key={category}
                                                label={category}
                                                size="small"
                                                sx={{ mr: 0.5, mb: 0.5 }}
                                            />
                                        ))}
                                    </Box>
                                    <Typography variant="h6" color="primary">
                                        ${product.priceInfo?.price}
                                    </Typography>
                                    {product.priceInfo?.originalPrice &&
                                        product.priceInfo.originalPrice !== "0" &&
                                        parseFloat(product.priceInfo.originalPrice) > parseFloat(product.priceInfo.price) && (
                                            <Typography
                                                variant="body2"
                                                color="text.secondary"
                                                sx={{ textDecoration: 'line-through' }}
                                            >
                                                ${product.priceInfo.originalPrice}
                                            </Typography>
                                        )}
                                    <Chip
                                        label={product.availability}
                                        color={product.availability === 'IN_STOCK' ? 'success' : 'error'}
                                        size="small"
                                        sx={{ mt: 1 }}
                                    />
                                </CardContent>
                            </Card>
                        </Grid>
                    );
                })}
            </Grid>
        );
    });

    return (
        <Container maxWidth="lg" sx={{ mt: 4 }}>
            <Grid container spacing={3}>
                {/* Filters Section */}
                <Grid item xs={12} md={3}>
                    <Card sx={{
                        p: 2,
                        boxShadow: '0 1px 3px rgba(0,0,0,0.08)',
                        borderRadius: '8px',
                    }}>
                        <Typography
                            variant="h6"
                            component="div"
                            sx={{
                                mb: 3,
                                fontWeight: 600,
                                fontSize: '20px',
                                color: '#212121'
                            }}
                        >
                            Filters
                        </Typography>

                        {/* Render the dynamic Filters component */}
                        <Filters
                            filterConfigs={filterConfigs}
                            selectedFilters={selectedFilters}
                            onFilterChange={handleFilterChange}
                        />
                    </Card>
                </Grid>

                {/* Search and Products Section */}
                <Grid item xs={12} md={9}>
                    {/* Use the isolated SearchInput component */}
                    <SearchInput onSearch={handleSearch} />

                    {/* Display search result count */}
                    {/* Results container with min-height to prevent layout shifts */}
                    <Box sx={{
                        minHeight: '50vh',
                        transition: 'all 0.3s ease',  /* Add transition for smooth changes */
                    }}>
                        {/* AI Filter Suggestions Component - updated to use filterConfigs */}
                        <AIFilterSuggestion
                            filterConfigs={filterConfigs}
                            selectedFilters={selectedFilters}
                            onFilterChange={handleFilterChange}
                            isVisible={showAIFilterSuggestions}
                            searchQuery={activeSearchQuery}
                            allProducts={products} // Pass all products for smart filtering
                        />

                        <Typography variant="body2" color="text.secondary" sx={{ mb: 2 }}>
                            {products.length ? `Found ${products.length} products` : 'No products found. Try a search!'}
                        </Typography>

                        {loading && <Typography>Loading products...</Typography>}
                        {error && <Typography color="error">Error: {error.message}</Typography>}

                        {/* Use memoized product grid component with stable click handler */}
                        <ProductGrid
                            products={currentProducts}
                            onProductClick={handleProductClick}
                        />
                    </Box>

                    {/* Pagination Controls */}
                    <Box sx={{ display: 'flex', justifyContent: 'center', alignItems: 'center', mt: 2 }}>
                        <Button
                            disabled={currentPage === 1}
                            onClick={() => handlePageChange(currentPage - 1)}
                        >
                            Previous
                        </Button>

                        {/* Advanced pagination with ellipsis */}
                        {(() => {
                            // Pages to always show - first page, last page, and pages around current
                            const pageButtons = [];
                            const showEllipsisStart = currentPage > 4;
                            const showEllipsisEnd = currentPage < totalPages - 3;

                            // Logic for which page numbers to show
                            for (let i = 1; i <= totalPages; i++) {
                                // Always show first and last page
                                if (i === 1 || i === totalPages) {
                                    pageButtons.push(
                                        <Button
                                            key={i}
                                            onClick={() => handlePageChange(i)}
                                            color={currentPage === i ? 'primary' : 'inherit'}
                                            sx={{ minWidth: '36px' }}
                                        >
                                            {i}
                                        </Button>
                                    );
                                    continue;
                                }

                                // Show pages around current page (current-1, current, current+1)
                                if (i >= currentPage - 1 && i <= currentPage + 1) {
                                    pageButtons.push(
                                        <Button
                                            key={i}
                                            onClick={() => handlePageChange(i)}
                                            color={currentPage === i ? 'primary' : 'inherit'}
                                            sx={{ minWidth: '36px' }}
                                        >
                                            {i}
                                        </Button>
                                    );
                                    continue;
                                }

                                // Show early pages if we're not too far
                                if (i < 5 && currentPage < 6) {
                                    pageButtons.push(
                                        <Button
                                            key={i}
                                            onClick={() => handlePageChange(i)}
                                            color={currentPage === i ? 'primary' : 'inherit'}
                                            sx={{ minWidth: '36px' }}
                                        >
                                            {i}
                                        </Button>
                                    );
                                    continue;
                                }

                                // Show late pages if we're close to the end
                                if (i > totalPages - 4 && currentPage > totalPages - 5) {
                                    pageButtons.push(
                                        <Button
                                            key={i}
                                            onClick={() => handlePageChange(i)}
                                            color={currentPage === i ? 'primary' : 'inherit'}
                                            sx={{ minWidth: '36px' }}
                                        >
                                            {i}
                                        </Button>
                                    );
                                    continue;
                                }

                                // Add ellipsis at the start if needed
                                if (i === 2 && showEllipsisStart) {
                                    pageButtons.push(
                                        <Box key="ellipsis-start" sx={{ mx: 1 }}>
                                            ...
                                        </Box>
                                    );
                                }

                                // Add ellipsis at the end if needed
                                if (i === totalPages - 1 && showEllipsisEnd) {
                                    pageButtons.push(
                                        <Box key="ellipsis-end" sx={{ mx: 1 }}>
                                            ...
                                        </Box>
                                    );
                                }
                            }

                            return pageButtons;
                        })()}

                        <Button
                            disabled={currentPage === totalPages || totalPages === 0}
                            onClick={() => handlePageChange(currentPage + 1)}
                        >
                            Next
                        </Button>
                    </Box>
                </Grid>
            </Grid>
        </Container>
    );
}