import { playCategories, type PlayRoute } from "../../play/components/playData"; import type { PublicDetail } from "@/lib/types"; import { normalizeConciergeAdvisor } from "../../concierge/components/conciergeTypes"; export type DetailPresentation = PublicDetail; const sharedGallery = [ "https://images.unsplash.com/photo-1500534623283-312aade485b7?auto=format&fit=crop&fm=jpg&q=80&w=1200&h=800", "https://images.unsplash.com/photo-1464822759023-fed622ff2c3b?auto=format&fit=crop&fm=jpg&q=80&w=1200&h=800", ]; function createRouteFallback(route: PlayRoute): DetailPresentation { return { key: route.id, eyebrow: "玩法推荐", duration: "5天4晚", title: route.title, subtitle: route.subtitle, intro: `围绕${route.title}安排一段节奏舒适的贵州探索,把自然景观、在地体验和小团服务串成一条完整路线。`, highlights: [ "核心景观串联,减少无效往返和重复换乘", "小团出行,按同行人的节奏灵活调整", "在地体验与舒适停留合理衔接", "行程前确认天气、装备和接送细节", ], included: ["行程内用车与接送服务", "方案中注明的景点门票和体验项目", "行前服务管家确认与途中跟进"], excluded: ["往返大交通及个人消费", "未列明餐食和自选体验", "因个人原因产生的额外费用"], notes: [ "贵州多山多雨,请准备防滑鞋、轻便雨具和薄外套。", "户外体验会根据天气和同行人体力适当调整。", "最终行程以出行日期、人数和资源确认结果为准。", ], gallery: Array.from(new Set([route.image, ...sharedGallery])).slice(0, 5), priceStartingValue: null, priceUnit: null, pricePeopleRange: "", priceDescription: "", conciergeAdvisor: null, }; } const routeFallbacks = new Map( playCategories.flatMap((category) => category.routes).map((route) => [route.id, createRouteFallback(route)]), ); function isRecord(value: unknown): value is Record { return Boolean(value) && typeof value === "object"; } function cleanText(value: unknown) { return typeof value === "string" && value.trim() ? value.trim() : ""; } function cleanList(value: unknown) { return Array.isArray(value) ? value.map((item) => cleanText(item)).filter(Boolean) : []; } function cleanGallery(value: unknown) { return Array.from( new Set( cleanList(value).filter((item) => /^https?:\/\//i.test(item)), ), ); } function cleanPriceStartingValue(value: unknown, fallback: number | null) { if (typeof value !== "number" || !Number.isFinite(value) || value < 0) return fallback; return Number(value.toFixed(2)); } function cleanPriceUnit(value: unknown, fallback: PublicDetail["priceUnit"]): PublicDetail["priceUnit"] { return value === "person" || value === "day" || value === "group" ? value : fallback; } function cleanOptionalText(value: unknown, fallback: string) { return cleanText(value) || fallback; } export function findWanfaRouteDetailFallback(routeId: string) { return routeFallbacks.get(routeId) ?? null; } export function normalizeDetailPresentation( value: unknown, fallback: DetailPresentation | null = null, ): DetailPresentation | null { const source = isRecord(value) ? value : {}; const key = cleanText(source.key) || fallback?.key || ""; const eyebrow = cleanText(source.eyebrow) || fallback?.eyebrow || ""; const duration = cleanText(source.duration) || fallback?.duration || ""; const title = cleanText(source.title) || fallback?.title || ""; const subtitle = cleanText(source.subtitle) || fallback?.subtitle || ""; const intro = cleanText(source.intro) || fallback?.intro || ""; const gallery = cleanGallery(source.gallery); const conciergeAdvisor = normalizeConciergeAdvisor(source.conciergeAdvisor); const priceStartingValue = cleanPriceStartingValue(source.priceStartingValue, fallback?.priceStartingValue ?? null); const priceUnit = cleanPriceUnit(source.priceUnit, fallback?.priceUnit ?? null); const pricePeopleRange = cleanOptionalText(source.pricePeopleRange, fallback?.pricePeopleRange ?? ""); const priceDescription = cleanOptionalText(source.priceDescription, fallback?.priceDescription ?? ""); if (!key || !eyebrow || !duration || !title || !subtitle || !intro) { return fallback; } return { key, eyebrow, duration, title, subtitle, intro, highlights: cleanList(source.highlights).length ? cleanList(source.highlights) : fallback?.highlights ?? [], included: cleanList(source.included).length ? cleanList(source.included) : fallback?.included ?? [], excluded: cleanList(source.excluded).length ? cleanList(source.excluded) : fallback?.excluded ?? [], notes: cleanList(source.notes).length ? cleanList(source.notes) : fallback?.notes ?? [], gallery: gallery.length ? gallery : fallback?.gallery ?? [], priceStartingValue, priceUnit, pricePeopleRange, priceDescription, conciergeAdvisor, }; }