import type { ChronicDisease, ExerciseFrequency, PainLevel, RiskLevel, SelfAssessmentRequest, WantedImprovement, } from "@/api/types"; export interface GuestSelfAssessmentDraft { assessment: SelfAssessmentRequest; provisionalRiskLevel: RiskLevel; /** 同一份草稿重送正式自評時沿用同一個 key。 */ idempotencyKey?: string; } export const GUEST_SELF_ASSESSMENT_DRAFT_KEY = "guest-self-assessment-draft"; const EXERCISE_FREQUENCY_VALUES: ExerciseFrequency[] = [ "none", "oneToTwo", "threeOrMore", ]; const PAIN_LEVEL_VALUES: PainLevel[] = ["none", "mild", "moderate", "severe"]; const RISK_LEVEL_VALUES: RiskLevel[] = ["low", "followUp", "consultation"]; const WANTED_IMPROVEMENT_VALUES: WantedImprovement[] = [ "strength", "balance", "cardio", "flexibility", "agility", "bodyComposition", ]; const CHRONIC_DISEASE_VALUES: ChronicDisease[] = [ "hypertension", "diabetes", "hyperlipidemia", "heartDisease", "osteoarthritis", "osteoporosis", ]; function isRecord(value: unknown): value is Record { return typeof value === "object" && value !== null; } function isOneOf( values: readonly T[], value: unknown, ): value is T { return typeof value === "string" && values.includes(value as T); } function isStringArray( values: readonly T[], value: unknown, ): value is T[] { return ( Array.isArray(value) && value.every((item) => isOneOf(values, item)) && new Set(value).size === value.length ); } function isValidAssessment(value: unknown): value is SelfAssessmentRequest { if (!isRecord(value)) { return false; } return ( isOneOf(EXERCISE_FREQUENCY_VALUES, value.exerciseFrequency) && isStringArray(WANTED_IMPROVEMENT_VALUES, value.wantedImprovements) && typeof value.hasFalls === "boolean" && isOneOf(PAIN_LEVEL_VALUES, value.painLevel) && isStringArray(CHRONIC_DISEASE_VALUES, value.chronicDiseases) ); } function isValidDraft(value: unknown): value is GuestSelfAssessmentDraft { if (!isRecord(value)) { return false; } return ( isValidAssessment(value.assessment) && isOneOf(RISK_LEVEL_VALUES, value.provisionalRiskLevel) && (value.idempotencyKey === undefined || typeof value.idempotencyKey === "string") ); } /** 依後端規則計算訪客階段的暫存結果;正式結果仍以後端回應為準。 */ export function calculateProvisionalRiskLevel( assessment: SelfAssessmentRequest, ): RiskLevel { if ( assessment.painLevel === "moderate" || assessment.painLevel === "severe" || assessment.chronicDiseases.length >= 2 ) { return "consultation"; } if (assessment.exerciseFrequency === "none" || assessment.hasFalls === true) { return "followUp"; } return "low"; } /** 僅在自評完成並確認送出後保存不含身分資料的訪客草稿。 */ export function saveGuestSelfAssessmentDraft( draft: GuestSelfAssessmentDraft, ): void { if (typeof window === "undefined") { return; } try { window.sessionStorage.setItem( GUEST_SELF_ASSESSMENT_DRAFT_KEY, JSON.stringify(draft), ); } catch { // 儲存空間不可用時不讓表單提交流程因瀏覽器安全設定而崩潰。 } } /** 讀取並驗證訪客草稿;格式不正確的資料視為不存在。 */ export function readGuestSelfAssessmentDraft(): GuestSelfAssessmentDraft | null { if (typeof window === "undefined") { return null; } try { const rawDraft = window.sessionStorage.getItem( GUEST_SELF_ASSESSMENT_DRAFT_KEY, ); if (!rawDraft) { return null; } const parsedDraft: unknown = JSON.parse(rawDraft); if (!isValidDraft(parsedDraft)) { return null; } const sanitizedDraft: GuestSelfAssessmentDraft = { assessment: { ...parsedDraft.assessment, wantedImprovements: [...parsedDraft.assessment.wantedImprovements], chronicDiseases: [...parsedDraft.assessment.chronicDiseases], }, provisionalRiskLevel: parsedDraft.provisionalRiskLevel, ...(parsedDraft.idempotencyKey ? { idempotencyKey: parsedDraft.idempotencyKey } : {}), }; saveGuestSelfAssessmentDraft(sanitizedDraft); return sanitizedDraft; } catch { return null; } } /** 正式保存成功後清除訪客草稿;登入取消或 API 失敗時不應呼叫。 */ export function clearGuestSelfAssessmentDraft(): void { if (typeof window === "undefined") { return; } try { window.sessionStorage.removeItem(GUEST_SELF_ASSESSMENT_DRAFT_KEY); } catch { // sessionStorage 不可用時沒有可清除的資料,保持流程可繼續。 } }