diff --git a/src/lib/database/carDatabase.ts b/src/lib/database/carDatabase.ts new file mode 100644 index 0000000..c623d89 --- /dev/null +++ b/src/lib/database/carDatabase.ts @@ -0,0 +1,333 @@ +/** + * Car Database Service + * Handles all car data operations and queries + */ + +import { Car, CarFilter, CarComparison, CarInventory } from './carTypes'; + +export class CarDatabase { + private cars: Car[] = []; + + /** + * Initialize database with sample data + */ + initialize(cars: Car[]): void { + this.cars = cars; + } + + /** + * Get all cars + */ + getAllCars(): Car[] { + return this.cars; + } + + /** + * Get car by ID + */ + getCarById(id: string): Car | undefined { + return this.cars.find((car) => car.id === id); + } + + /** + * Search cars by filter criteria + */ + filterCars(filter: CarFilter): Car[] { + return this.cars.filter((car) => { + // Make filter + if (filter.make && !filter.make.includes(car.make)) { + return false; + } + + // Model filter + if (filter.model && !filter.model.includes(car.model)) { + return false; + } + + // Year range filter + if (filter.yearRange) { + const [minYear, maxYear] = filter.yearRange; + if (car.year < minYear || car.year > maxYear) { + return false; + } + } + + // Price range filter + if (filter.priceRange) { + const [minPrice, maxPrice] = filter.priceRange; + if (car.price < minPrice || car.price > maxPrice) { + return false; + } + } + + // Transmission filter + if (filter.transmission && !filter.transmission.includes(car.transmission)) { + return false; + } + + // Fuel filter + if (filter.fuel && !filter.fuel.includes(car.fuel)) { + return false; + } + + // Body type filter + if (filter.bodyType && !filter.bodyType.includes(car.bodyType || '')) { + return false; + } + + // Drive type filter + if (filter.driveType && !filter.driveType.includes(car.driveType || '')) { + return false; + } + + // Seating filter + if (filter.seatCount && car.seating) { + const [minSeats, maxSeats] = filter.seatCount; + if (car.seating < minSeats || car.seating > maxSeats) { + return false; + } + } + + // Safety rating filter + if (filter.safetyRating && car.safetyRating && car.safetyRating < filter.safetyRating) { + return false; + } + + // Keyword search + if (filter.keyword) { + const keyword = filter.keyword.toLowerCase(); + const searchableFields = [ + car.make, + car.model, + car.bodyType, + car.color, + car.description, + ].filter(Boolean); + + const matches = searchableFields.some( + (field) => field && field.toString().toLowerCase().includes(keyword) + ); + + if (!matches) { + return false; + } + } + + return true; + }); + } + + /** + * Search cars by keyword + */ + searchCars(keyword: string): Car[] { + return this.filterCars({ keyword }); + } + + /** + * Get cars by make + */ + getCarsByMake(make: string): Car[] { + return this.cars.filter((car) => car.make.toLowerCase() === make.toLowerCase()); + } + + /** + * Get cars by fuel type + */ + getCarsByFuel(fuel: string): Car[] { + return this.cars.filter((car) => car.fuel === fuel); + } + + /** + * Get cars by body type + */ + getCarsByBodyType(bodyType: string): Car[] { + return this.cars.filter((car) => car.bodyType === bodyType); + } + + /** + * Get cars within price range + */ + getCarsByPriceRange(minPrice: number, maxPrice: number): Car[] { + return this.cars.filter((car) => car.price >= minPrice && car.price <= maxPrice); + } + + /** + * Sort cars by field + */ + sortCars(cars: Car[], sortBy: keyof Car, ascending: boolean = true): Car[] { + const sorted = [...cars].sort((a, b) => { + const aValue = a[sortBy]; + const bValue = b[sortBy]; + + if (typeof aValue === 'number' && typeof bValue === 'number') { + return ascending ? aValue - bValue : bValue - aValue; + } + + if (typeof aValue === 'string' && typeof bValue === 'string') { + return ascending ? aValue.localeCompare(bValue) : bValue.localeCompare(aValue); + } + + return 0; + }); + + return sorted; + } + + /** + * Get unique makes + */ + getUniqueMakes(): string[] { + const makes = new Set(this.cars.map((car) => car.make)); + return Array.from(makes).sort(); + } + + /** + * Get unique models for a make + */ + getUniqueModels(make: string): string[] { + const models = new Set( + this.cars.filter((car) => car.make === make).map((car) => car.model) + ); + return Array.from(models).sort(); + } + + /** + * Get unique fuel types + */ + getUniqueFuelTypes(): string[] { + const fuels = new Set(this.cars.map((car) => car.fuel)); + return Array.from(fuels).sort(); + } + + /** + * Get unique body types + */ + getUniqueBodyTypes(): string[] { + const bodyTypes = new Set(this.cars.filter((car) => car.bodyType).map((car) => car.bodyType)); + return Array.from(bodyTypes).sort(); + } + + /** + * Compare two or more cars + */ + compareCars(carIds: string[]): Car[] { + return carIds + .map((id) => this.getCarById(id)) + .filter((car) => car !== undefined) as Car[]; + } + + /** + * Get similar cars based on specifications + */ + getSimilarCars(carId: string, count: number = 3): Car[] { + const car = this.getCarById(carId); + if (!car) return []; + + const similar = this.cars + .filter((c) => c.id !== carId && c.make === car.make) + .slice(0, count); + + return similar.length >= count + ? similar + : this.cars.filter((c) => c.id !== carId && c.bodyType === car.bodyType).slice(0, count); + } + + /** + * Get top rated cars + */ + getTopRatedCars(count: number = 5): Car[] { + return [...this.cars] + .sort((a, b) => (b.userRating || 0) - (a.userRating || 0)) + .slice(0, count); + } + + /** + * Get most viewed cars + */ + getMostViewedCars(count: number = 5): Car[] { + return [...this.cars] + .sort((a, b) => (b.viewCount || 0) - (a.viewCount || 0)) + .slice(0, count); + } + + /** + * Get featured cars + */ + getFeaturedCars(count: number = 5): Car[] { + return this.cars.filter((car) => car.isFavorite).slice(0, count); + } + + /** + * Calculate average specifications for a make or body type + */ + calculateAverageSpecs( + cars: Car[] + ): Partial { + if (cars.length === 0) return {}; + + const specs: Partial = { + price: + cars.reduce((sum, car) => sum + car.price, 0) / cars.length, + horsepower: + cars.reduce((sum, car) => sum + (car.horsepower || 0), 0) / cars.length, + mpg: cars.reduce((sum, car) => sum + (car.mpg || 0), 0) / cars.length, + seating: + Math.round(cars.reduce((sum, car) => sum + (car.seating || 0), 0) / cars.length), + }; + + return specs; + } + + /** + * Add a new car to database + */ + addCar(car: Car): void { + this.cars.push(car); + } + + /** + * Update a car + */ + updateCar(id: string, updates: Partial): Car | undefined { + const index = this.cars.findIndex((car) => car.id === id); + if (index !== -1) { + this.cars[index] = { ...this.cars[index], ...updates, updatedAt: new Date() }; + return this.cars[index]; + } + return undefined; + } + + /** + * Delete a car + */ + deleteCar(id: string): boolean { + const index = this.cars.findIndex((car) => car.id === id); + if (index !== -1) { + this.cars.splice(index, 1); + return true; + } + return false; + } + + /** + * Get database statistics + */ + getStatistics() { + return { + totalCars: this.cars.length, + averagePrice: this.cars.reduce((sum, car) => sum + car.price, 0) / this.cars.length, + makes: this.getUniqueMakes().length, + models: this.cars.length, + fuelTypes: this.getUniqueFuelTypes().length, + bodyTypes: this.getUniqueBodyTypes().length, + averageHorsepower: + this.cars.reduce((sum, car) => sum + (car.horsepower || 0), 0) / this.cars.length, + averageMpg: + this.cars.reduce((sum, car) => sum + (car.mpg || 0), 0) / this.cars.length, + }; + } +} + +// Export singleton instance +export const carDatabase = new CarDatabase();