import { getPoiRepository } from '@/data/poi' import { clonePlanResponse, cloneTravelPreferences, normalizePlanResponse, normalizeTravelPreferences, type PlanningRequest, type PlanResponse, type TravelPreferences, } from '@/domain/travel' export const PLAN_STORAGE_KEY = 'guangming:last-plan' export const PREFERENCES_STORAGE_KEY = 'guangming:last-preferences' export const PLANNING_REQUEST_STORAGE_KEY = 'guangming:last-planning-request' const STORAGE_SCHEMA_VERSION = 1 interface StorageEnvelope { schemaVersion: typeof STORAGE_SCHEMA_VERSION datasetVersion: string savedAt: string payload: T } function isRecord(value: unknown): value is Record { return typeof value === 'object' && value !== null && !Array.isArray(value) } function createEnvelope(payload: T): StorageEnvelope { return { schemaVersion: STORAGE_SCHEMA_VERSION, datasetVersion: getPoiRepository().getDatasetMeta().datasetVersion, savedAt: new Date().toISOString(), payload, } } function readEnvelope(value: unknown): StorageEnvelope | null { if (!isRecord(value) || value.schemaVersion !== STORAGE_SCHEMA_VERSION) return null if (typeof value.datasetVersion !== 'string' || !value.datasetVersion.trim()) return null if (typeof value.savedAt !== 'string' || !Number.isFinite(Date.parse(value.savedAt))) return null if (!('payload' in value)) return null return value as unknown as StorageEnvelope } function readStorage(key: string): unknown { try { return uni.getStorageSync(key) } catch { return null } } function writeStorage(key: string, value: unknown): void { try { uni.setStorageSync(key, value) } catch (error) { const reason = error instanceof Error && error.message ? `:${error.message}` : '' throw new Error(`无法保存本机行程数据${reason}`) } } export function savePlan(plan: PlanResponse): void { const repository = getPoiRepository() const normalized = normalizePlanResponse(plan, repository) writeStorage(PLAN_STORAGE_KEY, createEnvelope(clonePlanResponse(normalized, repository))) } export function loadPlan(): PlanResponse | null { const repository = getPoiRepository() const envelope = readEnvelope(readStorage(PLAN_STORAGE_KEY)) if (!envelope || envelope.datasetVersion !== repository.getDatasetMeta().datasetVersion) return null try { return normalizePlanResponse(envelope.payload, repository) } catch { return null } } export function savePreferences(preferences: TravelPreferences): void { const normalized = normalizeTravelPreferences(preferences) writeStorage(PREFERENCES_STORAGE_KEY, createEnvelope(cloneTravelPreferences(normalized))) } export function loadPreferences(): TravelPreferences | null { const envelope = readEnvelope(readStorage(PREFERENCES_STORAGE_KEY)) if (!envelope) return null try { return normalizeTravelPreferences(envelope.payload) } catch { return null } } export function savePlanningRequest(request: PlanningRequest): void { const repository = getPoiRepository() const sanitized: PlanningRequest = { mode: request.mode, preferences: normalizeTravelPreferences(request.preferences), durationMinutes: request.durationMinutes, selectedPoiIds: [...request.selectedPoiIds], } if (sanitized.mode !== 'quick' && sanitized.mode !== 'custom') throw new Error('无法保存规划条件:规划模式无效') if (!Number.isInteger(sanitized.durationMinutes) || sanitized.durationMinutes < 120 || sanitized.durationMinutes > 600 || sanitized.durationMinutes % 30 !== 0) { throw new Error('无法保存规划条件:规划时长无效') } if (sanitized.mode === 'quick' && sanitized.selectedPoiIds.length > 0) throw new Error('无法保存规划条件:快速规划不能预选点位') if (sanitized.mode === 'custom' && (sanitized.selectedPoiIds.length < 2 || sanitized.selectedPoiIds.length > 8)) { throw new Error('无法保存规划条件:自选点位数量无效') } if (new Set(sanitized.selectedPoiIds).size !== sanitized.selectedPoiIds.length || sanitized.selectedPoiIds.some(poiId => !repository.getPoiById(poiId))) { throw new Error('无法保存规划条件:自选点位无效') } writeStorage(PLANNING_REQUEST_STORAGE_KEY, createEnvelope(sanitized)) } export function loadPlanningRequest(): PlanningRequest | null { const repository = getPoiRepository() const envelope = readEnvelope(readStorage(PLANNING_REQUEST_STORAGE_KEY)) if (!envelope || envelope.datasetVersion !== repository.getDatasetMeta().datasetVersion || !isRecord(envelope.payload)) return null try { const payload = envelope.payload const request: PlanningRequest = { mode: payload.mode as PlanningRequest['mode'], preferences: normalizeTravelPreferences(payload.preferences), durationMinutes: Number(payload.durationMinutes), selectedPoiIds: Array.isArray(payload.selectedPoiIds) ? payload.selectedPoiIds.map(String) : [], } if (request.mode !== 'quick' && request.mode !== 'custom') return null if (!Number.isInteger(request.durationMinutes) || request.durationMinutes < 120 || request.durationMinutes > 600 || request.durationMinutes % 30 !== 0) { return null } if (request.mode === 'quick' && request.selectedPoiIds.length > 0) return null if (request.mode === 'custom' && (request.selectedPoiIds.length < 2 || request.selectedPoiIds.length > 8)) return null if (new Set(request.selectedPoiIds).size !== request.selectedPoiIds.length || request.selectedPoiIds.some(poiId => !repository.getPoiById(poiId))) { return null } return request } catch { return null } }