import { create } from 'zustand'; import { persist } from 'zustand/middleware'; import type { ListingDetail } from './listings-api'; const MAX_COMPARE = 5; const MIN_COMPARE = 2; export interface ComparisonStats { priceRange: { min: number; max: number; avg: number }; areaRange: { min: number; max: number; avg: number }; pricePerM2Range: { min: number; max: number; avg: number } | null; } export interface ComparisonState { /** Listing IDs selected for comparison */ selectedIds: string[]; /** Cached listing details (loaded when user navigates to compare page) */ listings: ListingDetail[]; /** Whether listings data is loading */ isLoading: boolean; /** Error message if fetching failed */ error: string | null; /** Add a listing ID to the comparison set (max 5) */ addToCompare: (id: string) => boolean; /** Remove a listing ID from the comparison set */ removeFromCompare: (id: string) => void; /** Check if a listing ID is already selected */ isSelected: (id: string) => boolean; /** Clear all selections */ clearAll: () => void; /** Whether the compare button should be active */ canCompare: () => boolean; /** Whether more listings can be added */ canAdd: () => boolean; /** Set fetched listings data */ setListings: (listings: ListingDetail[]) => void; /** Set loading state */ setLoading: (loading: boolean) => void; /** Set error state */ setError: (error: string | null) => void; } export function computeComparisonStats(listings: ListingDetail[]): ComparisonStats | null { if (listings.length < MIN_COMPARE) return null; const prices = listings.map((l) => Number(l.priceVND)).filter(Number.isFinite); const areas = listings.map((l) => l.property.areaM2).filter(Number.isFinite); const pricesPerM2 = listings .map((l) => l.pricePerM2) .filter((v): v is number => v != null && Number.isFinite(v)); if (prices.length === 0 || areas.length === 0) return null; return { priceRange: { min: Math.min(...prices), max: Math.max(...prices), avg: Math.round(prices.reduce((a, b) => a + b, 0) / prices.length), }, areaRange: { min: Math.min(...areas), max: Math.max(...areas), avg: Math.round((areas.reduce((a, b) => a + b, 0) / areas.length) * 100) / 100, }, pricePerM2Range: pricesPerM2.length > 0 ? { min: Math.min(...pricesPerM2), max: Math.max(...pricesPerM2), avg: Math.round(pricesPerM2.reduce((a, b) => a + b, 0) / pricesPerM2.length), } : null, }; } export const useComparisonStore = create()( persist( (set, get) => ({ selectedIds: [], listings: [], isLoading: false, error: null, addToCompare: (id: string) => { const { selectedIds } = get(); if (selectedIds.length >= MAX_COMPARE || selectedIds.includes(id)) return false; set({ selectedIds: [...selectedIds, id], error: null }); return true; }, removeFromCompare: (id: string) => { set((state) => ({ selectedIds: state.selectedIds.filter((sid) => sid !== id), listings: state.listings.filter((l) => l.id !== id), })); }, isSelected: (id: string) => get().selectedIds.includes(id), clearAll: () => set({ selectedIds: [], listings: [], error: null }), canCompare: () => get().selectedIds.length >= MIN_COMPARE, canAdd: () => get().selectedIds.length < MAX_COMPARE, setListings: (listings: ListingDetail[]) => set({ listings, isLoading: false, error: null }), setLoading: (isLoading: boolean) => set({ isLoading }), setError: (error: string | null) => set({ error, isLoading: false }), }), { name: 'goodgo-compare', partialize: (state) => ({ selectedIds: state.selectedIds }), }, ), ); export { MAX_COMPARE, MIN_COMPARE };