import type { Property } from '../types/real-estate.js';
import { zillowService } from '../services/zillow-service.js';
import { DataSourceError, ValidationError, logError, formatUserError } from '../utils/errors.js';
/**
* Property Details Tool - Get comprehensive property information
*
* Retrieves detailed property information including photos, history,
* and enhanced features from Zillow API.
*/
export async function getPropertyDetails(propertyId: string): Promise<Property | null> {
if (!propertyId || propertyId.trim().length === 0) {
throw new ValidationError('Property ID is required', 'propertyId');
}
console.log(`Getting detailed property information for ID: ${propertyId}`);
try {
// Get basic property details
const property = await zillowService.getPropertyDetails(propertyId);
if (!property) {
console.log(`Property not found: ${propertyId}`);
return null;
}
// Enhance with additional data in parallel
const [photos, priceHistory, comparables] = await Promise.allSettled([
zillowService.getPropertyPhotos(propertyId),
zillowService.getPriceHistory(propertyId),
zillowService.getComparableProperties(propertyId)
]);
// Add photos if available
if (photos.status === 'fulfilled' && photos.value.length > 0) {
property.photos = photos.value;
}
// Add price history if available
if (priceHistory.status === 'fulfilled' && priceHistory.value.length > 0) {
// Transform price history to our format
property.priceHistory = priceHistory.value.map(entry => ({
date: new Date(entry.date).toISOString(),
price: entry.price,
event: entry.event || 'price_change'
}));
}
// Log any failed enhancements (but don't fail the whole request)
if (photos.status === 'rejected') {
logError(photos.reason, 'Property Photos Enhancement');
}
if (priceHistory.status === 'rejected') {
logError(priceHistory.reason, 'Price History Enhancement');
}
if (comparables.status === 'rejected') {
logError(comparables.reason, 'Comparables Enhancement');
}
console.log(`Retrieved enhanced property details for ${property.address.street || 'property'}`);
return property;
} catch (error) {
logError(error as Error, 'Property Details');
if (error instanceof ValidationError) {
throw error; // Re-throw validation errors as-is
}
if (error instanceof DataSourceError) {
throw new Error(formatUserError(error));
}
throw new Error('Failed to retrieve property details. Please try again.');
}
}
/**
* Get property comparables (similar properties)
*/
export async function getPropertyComparables(propertyId: string): Promise<Property[]> {
if (!propertyId || propertyId.trim().length === 0) {
throw new ValidationError('Property ID is required', 'propertyId');
}
console.log(`Getting comparable properties for ID: ${propertyId}`);
try {
const comparables = await zillowService.getComparableProperties(propertyId);
console.log(`Found ${comparables.length} comparable properties`);
return comparables;
} catch (error) {
logError(error as Error, 'Property Comparables');
if (error instanceof ValidationError) {
throw error;
}
// For comparables, we can return empty array on error
console.warn('Failed to get comparables, returning empty array');
return [];
}
}
/**
* Enhanced property search with details
* Combines search with immediate detail retrieval for better user experience
*/
export async function searchPropertiesWithDetails(
location: string,
options: {
minPrice?: number;
maxPrice?: number;
minBeds?: number;
maxResults?: number;
} = {}
): Promise<Property[]> {
// Import search function here to avoid circular imports
const { searchProperties } = await import('./property-search.js');
try {
// First, search for properties
const searchQuery = {
location,
minPrice: options.minPrice,
maxPrice: options.maxPrice,
minBeds: options.minBeds,
page: 1
};
const properties = await searchProperties(searchQuery);
if (properties.length === 0) {
return [];
}
// Limit results to prevent too many API calls
const maxResults = options.maxResults || 5;
const limitedProperties = properties.slice(0, maxResults);
// Enhance each property with additional details
const enhancedProperties = await Promise.allSettled(
limitedProperties.map(async (property) => {
if (!property.id) return property;
try {
const detailed = await getPropertyDetails(property.id);
return detailed || property; // Fallback to original if details fail
} catch {
return property; // Fallback to original on any error
}
})
);
// Return only fulfilled results
return enhancedProperties
.filter((result): result is PromiseFulfilledResult<Property> =>
result.status === 'fulfilled'
)
.map(result => result.value);
} catch (error) {
logError(error as Error, 'Enhanced Property Search');
throw new Error(formatUserError(error as Error));
}
}