import type { City } from "./cities"; import { avatarForIndex, avatarForName } from "./avatars"; export type SocialMode = "coffeechat" | "localGuide" | "cityWalk"; export type ActivityMode = "offline" | "online" | "hybrid"; export interface CityProfileRecord { id?: string; name?: string; age?: number; location?: string; tags?: string[]; bio?: string; photo?: string; } export interface CitySocialMember { id: string; name: string; age?: number; role: string; location: string; photo: string; bio: string; availability: string; responseTime: string; focus: string[]; openTo: SocialMode[]; } export interface CityActivity { id: string; title: string; mode: ActivityMode; city: string; date: string; time: string; venue: string; organizer: string; description: string; rsvpCount: number; maxAttendees?: number; } const FALLBACK_NAMES = [ "林晓雨", "陈浩然", "王思琪", "张明远", "李雅婷", "刘子轩", "周雨桐", "吴俊杰", "郑诗涵", "孙宇航", ]; const ROLE_BY_INDEX = [ "产品设计师", "独立开发者", "内容创作者", "远程运营", "自由摄影师", "数据顾问", ]; const COFFEE_FOCUS = [ ["远程工作节奏", "城市落地", "产品设计"], ["独立开发", "副业收入", "咖啡馆办公"], ["内容创作", "本地生活", "社群连接"], ["创业项目", "远程团队", "效率工具"], ]; const GUIDE_FOCUS = [ ["老街路线", "本地咖啡", "短租避坑"], ["共享办公", "夜生活", "美食路线"], ["城市漫步", "摄影机位", "周末短途"], ["交通动线", "生活区", "新朋友局"], ]; const CITY_WALK_FOCUS = [ ["Citywalk", "老城徒步", "街区观察"], ["周末徒步", "轻户外", "补给路线"], ["城市漫步", "摄影路线", "本地小店"], ["公园路线", "日落机位", "低强度同行"], ]; function citySeed(city: City): number { return city.idNumber || city.id || city.name.charCodeAt(0); } export function getCitySocialSummary(city: City) { const base = Math.max(1, city.nomadsNow); const seed = citySeed(city); return { coffeechat: Math.max(4, Math.round(base * 0.055) + (seed % 4)), localGuide: Math.max(2, Math.round(base * 0.022) + (seed % 3)), cityWalk: Math.max(3, Math.round(base * 0.032) + (seed % 5)), }; } function fallbackMember(city: City, index: number): CitySocialMember { const seed = citySeed(city) + index; const name = FALLBACK_NAMES[seed % FALLBACK_NAMES.length]; const isGuideFirst = index % 3 === 1; const openTo: SocialMode[] = index % 2 === 0 ? ["coffeechat", "cityWalk"] : isGuideFirst ? ["localGuide"] : ["coffeechat"]; if (index % 4 === 0 && !openTo.includes("localGuide")) openTo.push("localGuide"); const focus = openTo.includes("cityWalk") ? CITY_WALK_FOCUS[index % CITY_WALK_FOCUS.length] : openTo.includes("localGuide") ? GUIDE_FOCUS[index % GUIDE_FOCUS.length] : COFFEE_FOCUS[index % COFFEE_FOCUS.length]; return { id: `fallback-${city.slug || city.name}-${index}`, name, age: 25 + (seed % 11), role: ROLE_BY_INDEX[index % ROLE_BY_INDEX.length], location: city.name, photo: avatarForIndex(seed + index), bio: `目前在${city.name}旅居/居住,资料开放,欢迎同城线下见面。`, availability: index % 2 === 0 ? "本周可约 2 次" : "周末更方便", responseTime: index % 3 === 0 ? "通常当天回复" : "通常 24 小时内回复", focus, openTo, }; } export function getFallbackCitySocialMembers(city: City): CitySocialMember[] { return Array.from({ length: 8 }, (_, index) => fallbackMember(city, index)); } function profileOpenTo(profile: CityProfileRecord, index: number): SocialMode[] { const tags = (profile.tags || []).join(" "); const modes = new Set(); if (/coffee|咖啡|聊天|线下/.test(tags)) modes.add("coffeechat"); if (/地陪|向导|探店|游玩|路线|本地/.test(tags)) modes.add("localGuide"); if (/citywalk|徒步|漫步|散步|户外|hiking/i.test(tags)) modes.add("cityWalk"); if (modes.size === 0) { modes.add(index % 3 === 0 ? "cityWalk" : index % 2 === 0 ? "coffeechat" : "localGuide"); if (index % 3 === 0) modes.add("localGuide"); } return Array.from(modes); } function profileToMember(city: City, profile: CityProfileRecord, index: number): CitySocialMember { const name = profile.name || "社区成员"; const openTo = profileOpenTo(profile, index); return { id: profile.id || `${city.slug || city.name}-profile-${index}`, name, age: profile.age, role: profile.tags?.find((tag) => !["数字游民", city.name, "可线下见面"].includes(tag)) || "数字游民", location: profile.location || city.name, photo: profile.photo || avatarForName(name), bio: profile.bio || `目前在${city.name},欢迎同城咖啡或城市散步。`, availability: index % 2 === 0 ? "本周可约" : "提前 1 天约", responseTime: index % 3 === 0 ? "通常当天回复" : "通常 24 小时内回复", focus: openTo.includes("cityWalk") ? CITY_WALK_FOCUS[index % CITY_WALK_FOCUS.length] : openTo.includes("localGuide") ? GUIDE_FOCUS[index % GUIDE_FOCUS.length] : COFFEE_FOCUS[index % COFFEE_FOCUS.length], openTo, }; } export function getCitySocialMembers(city: City, profiles: CityProfileRecord[]): CitySocialMember[] { const cityProfiles = profiles.filter((profile) => { const location = profile.location || ""; const tags = profile.tags || []; return location.includes(city.name) || tags.some((tag) => tag.includes(city.name)); }); const merged = [ ...cityProfiles.map((profile, index) => profileToMember(city, profile, index)), ...getFallbackCitySocialMembers(city), ]; const seen = new Set(); return merged.filter((member) => { const key = `${member.name}-${member.location}`; if (seen.has(key)) return false; seen.add(key); return true; }); } export function getFallbackCityActivities(city: City): CityActivity[] { const seed = citySeed(city); return [ { id: `fallback-${city.slug || city.name}-offline`, title: `${city.name}同城咖啡与落地经验局`, mode: "offline", city: city.name, date: "2026-07-15", time: "19:30", venue: `${city.name}核心生活区咖啡馆`, organizer: `${city.name}城市主理人`, description: `围绕${city.name}租房、办公、社群和长期停留做小规模线下交流。`, rsvpCount: 8 + (seed % 14), maxAttendees: 28, }, { id: `fallback-${city.slug || city.name}-online`, title: `${city.name}线上问答房`, mode: "online", city: city.name, date: "2026-07-18", time: "20:00", venue: "MiroTalk 线上房间", organizer: "NomadCNA", description: `给准备去${city.name}的人做一次线上答疑,集中聊预算、住区、网络和社交入口。`, rsvpCount: 18 + (seed % 20), maxAttendees: 80, }, { id: `fallback-${city.slug || city.name}-hybrid`, title: `${city.name}周末城市漫步`, mode: "hybrid", city: city.name, date: "2026-07-21", time: "15:00", venue: `${city.name}本地生活路线`, organizer: "同城成员自发", description: "开放给新到城市的成员,线下同行,线上同步路线和问答。", rsvpCount: 6 + (seed % 10), maxAttendees: 18, }, ]; } export function normalizeCityActivities(city: City, meetups: Array>): CityActivity[] { return meetups .filter((meetup) => String(meetup.city || city.name) === city.name) .map((meetup, index) => { const rawMode = String(meetup.mode || "offline"); const mode: ActivityMode = rawMode === "online" ? "online" : rawMode === "hybrid" ? "hybrid" : "offline"; return { id: String(meetup.id || `${city.slug || city.name}-meetup-${index}`), title: String(meetup.title || meetup.venue || `${city.name}同城活动`), mode, city: String(meetup.city || city.name), date: String(meetup.date || "时间待定"), time: String(meetup.time || ""), venue: String(meetup.venue || (mode === "online" ? "线上房间" : "活动地点待定")), organizer: String(meetup.organizer || `${city.name}城市主理人`), description: String(meetup.description || "同城成员发起的交流活动。"), rsvpCount: Number(meetup.rsvpCount || 0), maxAttendees: meetup.maxAttendees ? Number(meetup.maxAttendees) : undefined, }; }); } export function getCityActivities(city: City, meetups: Array> = []): CityActivity[] { const normalized = normalizeCityActivities(city, meetups); const fallback = getFallbackCityActivities(city); const merged = [...normalized, ...fallback]; const seen = new Set(); return merged.filter((activity) => { const key = `${activity.title}-${activity.mode}`; if (seen.has(key)) return false; seen.add(key); return true; }); }