import type { ProductFilters, ProductSearchResult, SearchProductsResponse } from "./types.js"; import { extractWidthInches, formatWidthInches } from "./product-metrics.js"; export interface ResponseInput { query: string; filters: ProductFilters; limit: number; maxSearchPages: number; results: ProductSearchResult[]; filteredOutCount: number; warnings: string[]; now?: () => Date; } export function createResponse(input: ResponseInput): SearchProductsResponse { return { query: input.query, filters: input.filters, limit: input.limit, maxSearchPages: input.maxSearchPages, results: input.results, filteredOutCount: input.filteredOutCount, warnings: input.warnings, source: { site: "amazon.com", scrapedAt: (input.now ?? (() => new Date()))().toISOString(), automation: "web-automation/CloakBrowser" } }; } function formatFilters(filters: ProductFilters): string { const parts = [ filters.minRating !== undefined ? `rating ${filters.ratingComparison ?? "gte"} ${filters.minRating}` : "", filters.minReviews !== undefined ? `reviews ${filters.reviewCountComparison ?? "gte"} ${filters.minReviews}` : "", filters.maxPrice !== undefined ? `price <= $${filters.maxPrice}` : "", filters.maxUnitPrice !== undefined ? `unit price <= $${filters.maxUnitPrice}` : "", filters.minWidthInches !== undefined ? `width ${filters.widthComparison ?? "gte"} ${filters.minWidthInches} inches` : "", filters.requirePrime ? "Prime delivery" : "", filters.requireFreeDelivery ? "free delivery" : "", filters.deliveryBy ? `delivery by ${filters.deliveryBy}` : "", filters.sortBy === "price" ? "sort by price" : "" ].filter(Boolean); return parts.length > 0 ? parts.join(", ") : "none"; } function compactText(value: string): string { return value.replace(/\s+/g, " ").trim(); } function marker(passes: boolean | undefined, enabled: boolean): string { if (!enabled) { return ""; } return passes ? " OK" : " NO"; } function widthCell(product: ProductSearchResult, filters: ProductFilters): string { const width = extractWidthInches(product); const passes = width !== undefined && (filters.widthComparison === "gt" ? width > (filters.minWidthInches ?? 0) : width >= (filters.minWidthInches ?? 0)); return `${formatWidthInches(width)}${marker(passes, filters.minWidthInches !== undefined)}`; } function primeCell(product: ProductSearchResult, filters: ProductFilters): string { if (product.delivery?.prime) { return `Prime${marker(true, Boolean(filters.requirePrime))}`; } return `not verified${marker(false, Boolean(filters.requirePrime))}`; } function deliveryCell(product: ProductSearchResult, filters: ProductFilters): string { const display = product.delivery?.display ?? "unknown"; if (!filters.deliveryBy) { return display; } const normalized = display.toLowerCase(); const passes = filters.deliveryBy === "today" ? /\btoday\b|same[- ]day/.test(normalized) : filters.deliveryBy === "tomorrow" || filters.deliveryBy === "overnight" ? /\btomorrow\b|overnight|next[- ]day|one[- ]day/.test(normalized) : normalized.includes(filters.deliveryBy.toLowerCase()); return `${display}${marker(passes, true)}`; } function resultBlocks(products: ProductSearchResult[], filters: ProductFilters): string[] { return products.flatMap((product, index) => [ `${index + 1}. ${compactText(product.title)}`, `Price: ${product.price?.display ?? "unknown"}`, `Rating: ${product.rating ?? "unknown"} stars`, `Reviews: ${product.reviewCount?.toLocaleString("en-US") ?? "unknown"}`, `Width: ${widthCell(product, filters)}`, `Prime: ${primeCell(product, filters)}`, `Delivery: ${compactText(deliveryCell(product, filters))}`, `Link: ${product.url}`, "" ]); } function metadataLines(products: ProductSearchResult[]): string[] { const lines: string[] = []; for (const product of products) { const notes = [ product.missingFields.length > 0 ? `missing ${product.missingFields.join(", ")}` : "", product.isSponsored ? "sponsored" : "", product.extractionNotes.length > 0 ? product.extractionNotes.join("; ") : "" ].filter(Boolean); if (notes.length > 0) { lines.push(`- ${product.title}: ${notes.join("; ")}`); } } return lines; } export function createMarkdownReport(response: SearchProductsResponse): string { const lines = [ `# Amazon Shopping Results`, "", `Query: ${response.query}`, `Filters: ${formatFilters(response.filters)}`, `Results returned: ${response.results.length} (filtered out: ${response.filteredOutCount})`, response.warnings.length > 0 ? `Warnings: ${response.warnings.join("; ")}` : "", "", "## Best Matches", "", response.results.length > 0 ? "" : "No products matched all requested filters.", ...resultBlocks(response.results, response.filters), "", ...metadataLines(response.results) ].filter((line) => line !== ""); return `${lines.join("\n")}\n`; }