import {
  createContext,
  useContext,
  useEffect,
  useMemo,
  useReducer,
  type ReactNode,
} from "react";
import { services } from "./services";
import { paceSecondsForStep, resolveTrainingProgress } from "./training-engine";
import type {
  AgentContextSnapshot,
  AppState,
  ConversationMessage,
  PlanAdjustmentData,
  RouteId,
  TrainingAnalysisData,
} from "./types";

const iso = (offset = 0) => {
  const d = new Date();
  d.setDate(d.getDate() + offset);
  return d.toISOString().slice(0, 10);
};

const timeNow = () =>
  new Date().toLocaleTimeString("zh-CN", {
    hour: "2-digit",
    minute: "2-digit",
    hour12: false,
  });
const profileSignature = (profile: AppState["profile"]) =>
  JSON.stringify(profile);

const buildDemoCycle = (
  weeklyItems: AppState["planItems"],
  totalWeeks: number,
) =>
  Array.from({ length: totalWeeks }, (_, weekOffset) =>
    weeklyItems.map((item, index) => {
      const date = new Date(`${item.date}T12:00:00`);
      date.setDate(date.getDate() + weekOffset * 7);
      const growth = 1 + Math.min(0.16, weekOffset * 0.025);
      const distance = Number((item.distance * growth).toFixed(1));
      return {
        ...item,
        id: `${item.id}-w${weekOffset + 1}`,
        date: date.toISOString().slice(0, 10),
        distance,
        duration: `${Math.max(25, Math.round(Number(item.duration.match(/\d+/)?.[0] ?? 45) * growth))} 分钟`,
        load: Math.round(item.load * growth),
        status: weekOffset === 0 ? item.status : ("confirmed" as const),
        weekIndex: weekOffset + 1,
        changed: false,
      };
    }),
  ).flat();

export const buildAgentContext = (state: AppState): AgentContextSnapshot => ({
  generatedAt: new Date().toISOString(),
  profile: { ...state.profile },
  device: {
    provider: state.device.provider,
    connectedProviders: [...state.device.connectedProviders],
    syncSources: [...state.device.syncSources],
    status: state.device.status,
    lastSyncAt: state.device.lastSyncAt,
    weather: state.device.weather ? { ...state.device.weather } : null,
    sleepHours: state.device.sleepHours,
    hrvMs: state.device.hrvMs,
    restingHeartRate: state.device.restingHeartRate,
  },
  recovery: {
    fatigue: state.recoveryFatigue,
    updatedAt: state.recoveryUpdatedAt,
  },
  plan: state.planItems.map(
    ({ id, date, title, distance, pace, duration, status, version }) => ({
      id,
      date,
      title,
      distance,
      pace,
      duration,
      status,
      version,
    }),
  ),
  nutrition: {
    hydrationMl: state.hydrationMl,
    completion: state.nutritionCompletion,
    completedNodes: [...state.completedNutritionNodes],
  },
});

export const createInitialState = (): AppState => ({
  route: "HOME-01",
  stack: [],
  authMode:
    typeof window !== "undefined" &&
    Boolean(window.localStorage.getItem("running-ai-access-token"))
      ? "authenticated"
      : "guest",
  activeTab: "home",
  demo: 2,
  toast: "",
  language: "zh-CN",
  boundPhone: "",
  selectedLegalDocument: "用户协议",
  profileComplete: true,
  profile: {
    goal: "完成半程马拉松 1:50",
    frequency: 3,
    experience: "3–5 年",
    weeklyKm: 32,
    gender: "male",
    heightCm: 175,
    weightKg: 68,
    sweat: 3,
    diet: "均衡饮食",
  },
  device: {
    provider: "高驰",
    connectedProviders: ["高驰"],
    syncSources: ["高驰 Pace 3"],
    status: "synced",
    lastSyncAt: "今天 08:32",
    gpsGranted: true,
    notificationGranted: true,
    authorizingProvider: "",
    weather: { temperature: 23, humidity: 62, condition: "多云" },
    sleepHours: 7.2,
    hrvMs: 58,
    restingHeartRate: 52,
  },
  planVersion: 3,
  planItems: buildDemoCycle(
    [
      {
        id: "plan-tempo",
        date: iso(0),
        title: "8 公里轻松跑",
        distance: 8,
        pace: "5′50″",
        duration: "48 分钟",
        status: "confirmed",
        load: 54,
        version: 3,
        structure: ["动态热身 8 分钟", "轻松跑 8 km", "拉伸 6 分钟"],
      },
      {
        id: "plan-easy",
        date: iso(2),
        title: "阈值跑",
        distance: 10,
        pace: "4′45″",
        duration: "52 分钟",
        status: "confirmed",
        load: 86,
        version: 3,
        structure: ["热身 2 km", "阈值跑 6 km", "放松跑 2 km"],
      },
      {
        id: "plan-long",
        date: iso(4),
        title: "长距离跑",
        distance: 16,
        pace: "5′30″",
        duration: "90 分钟",
        status: "confirmed",
        load: 108,
        version: 3,
        structure: ["轻松跑 12 km", "稳态跑 3 km", "放松 1 km"],
      },
    ],
    8,
  ),
  planProgram: {
    title: "半程马拉松计划",
    phase: "基础期",
    currentWeek: 3,
    totalWeeks: 8,
    summary: "每周 3 次 · 根据当前档案生成",
    weeklyLoad: 248,
    basis: [
      "目标：完成半程马拉松 1:50",
      "频率：每周 3 次",
      "周跑量：32 km",
      "高驰 Pace 3",
    ],
  },
  planGenerationStatus: "ready",
  planProfileSignature: profileSignature({
    goal: "完成半程马拉松 1:50",
    frequency: 3,
    experience: "3–5 年",
    weeklyKm: 32,
    gender: "male",
    heightCm: 175,
    weightKg: 68,
    sweat: 3,
    diet: "均衡饮食",
  }),
  planGeneratedAt: "今天 07:08",
  activities: [
    {
      id: "activity-last",
      planItemId: "plan-last",
      date: iso(-2),
      distance: 6.12,
      duration: "34:28",
      pace: "5′38″",
      heartRate: 142,
      source: "高驰 Pace 3",
      nutritionCompletion: 75,
    },
    {
      id: "activity-history-tempo",
      planItemId: "history-tempo",
      date: iso(-8),
      distance: 8.02,
      duration: "45:36",
      pace: "5′41″",
      heartRate: 148,
      source: "高驰 Pace 3",
      nutritionCompletion: 100,
    },
    {
      id: "activity-history-long",
      planItemId: "history-long",
      date: iso(-16),
      distance: 13.18,
      duration: "1:16:42",
      pace: "5′49″",
      heartRate: 145,
      source: "高驰 Pace 3",
      nutritionCompletion: 80,
    },
    {
      id: "activity-history-previous-month",
      planItemId: "history-easy",
      date: iso(-32),
      distance: 7.36,
      duration: "41:25",
      pace: "5′38″",
      heartRate: 139,
      source: "高驰 Pace 3",
      nutritionCompletion: 90,
    },
  ],
  trainingView: "plan",
  selectedDate: iso(0),
  trainingStartReminder: {
    planId: "",
    displayTime: "",
    scheduledAt: "",
    status: "idle",
  },
  trainingAnalysis: null,
  conversation: [
    {
      id: "m1",
      role: "agent",
      text: "早上好。我结合昨晚恢复和今天的计划，整理了今天的行动建议。",
      time: "07:10",
    },
    {
      id: "m2",
      role: "agent",
      text: "恢复数据更新于今天 08:32，目前适合按计划训练。",
      time: "07:10",
      card: "recovery",
    },
    {
      id: "m3",
      role: "agent",
      text: "今天的主任务是 8 公里轻松跑，先确认再进入训练准备。",
      time: "07:11",
      card: "training",
    },
    {
      id: "m4",
      role: "agent",
      text: "补给已按天气、出汗自评和训练时长生成，可在开始前检查。",
      time: "07:12",
      card: "nutrition",
    },
  ],
  homeSessionDate: iso(0),
  conversationArchive: [],
  conversationResetAt: null,
  historyResetStatus: "idle",
  suggestedQuestions: [],
  suggestedQuestionsStatus: "idle",
  suggestedQuestionsUpdatedAt: "",
  composerDraft: "",
  hydrationMl: 1200,
  hydrationByDate: { [iso(0)]: 1200 },
  mealCalories: 1200,
  trainingMealCalories: 0,
  mealEntries: [
    {
      id: "breakfast",
      name: "早餐",
      date: iso(0),
      foods: [
        { id: "oats", name: "燕麦", amount: "50 g", caloriesKcal: 190, hydrationMl: 0, carbohydrateG: 34, proteinG: 7, fatG: 4, source: "user" },
        { id: "egg", name: "鸡蛋", amount: "2 个", caloriesKcal: 140, hydrationMl: 0, carbohydrateG: 1, proteinG: 13, fatG: 10, source: "user" },
        { id: "banana", name: "香蕉", amount: "1 根", caloriesKcal: 105, hydrationMl: 0, carbohydrateG: 27, proteinG: 1, fatG: 0, source: "user" },
      ],
    },
    {
      id: "lunch",
      name: "午餐",
      date: iso(0),
      foods: [
        { id: "rice", name: "米饭", amount: "200 g", caloriesKcal: 232, hydrationMl: 0, carbohydrateG: 52, proteinG: 5, fatG: 1, source: "user" },
        { id: "chicken", name: "鸡胸肉", amount: "150 g", caloriesKcal: 248, hydrationMl: 0, carbohydrateG: 0, proteinG: 46, fatG: 5, source: "user" },
        { id: "broccoli", name: "西兰花", amount: "150 g", caloriesKcal: 51, hydrationMl: 0, carbohydrateG: 10, proteinG: 4, fatG: 1, source: "user" },
      ],
    },
  ],
  selectedMealId: "",
  recoveryFatigue: 6,
  recoveryUpdatedAt: "今天 08:32",
  nutritionCompletion: 55,
  completedNutritionNodes: ["pre-water"],
  completedNutritionNodesByDate: { [iso(0)]: ["pre-water"] },
  nutritionFeedbackPending: {
    active: false,
    sourceRoute: null,
    planDate: "",
    activityId: "",
  },
  selectedFuelProduct: "康比特能量胶",
  nutritionPlan: null,
  nutritionPlansByDate: {},
  nutritionPlanStatus: "idle",
  selectedNutritionNodeId: "",
  nutritionReminderReviewsByDate: {},
  live: {
    sessionId: "",
    status: "ready",
    elapsed: 0,
    distance: 0,
    heartRate: 128,
    energy: 0,
    currentStepIndex: 0,
    stepStartedAt: 0,
    stepStartedDistance: 0,
  },
  reminder: {
    id: "fuel-45",
    nodeId: "during-electrolyte",
    enabled: true,
    triggerSecond: 8,
    triggerLabel: "第 30 分钟",
    type: "补液",
    amount: "电解质饮料 150 ml",
    productName: "康比特电解质饮料",
    status: "scheduled",
    vibrationCount: 0,
  },
  feedback: {
    rpe: 13,
    thirst: 5,
    soreness: "轻微",
    gi: "舒适",
    submitted: false,
  },
  lastCompletedActivityId: "",
  analysisContext: null,
  report: {
    status: "not_uploaded",
    consent: false,
    reportId: "",
    fileName: "",
    source: "",
    uploadedAt: "",
    fields: [],
  },
  network: "online",
  agentFailure: false,
  agentRetrying: false,
  lastFailedPrompt: "",
  safetyBlocked: false,
});

type Action =
  | { type: "NAVIGATE"; route: RouteId; tab?: AppState["activeTab"] }
  | { type: "BACK" }
  | { type: "PATCH"; patch: Partial<AppState> }
  | { type: "PROFILE"; patch: Partial<AppState["profile"]> }
  | {
      type: "PLAN_GENERATED";
      items: AppState["planItems"];
      program: AppState["planProgram"];
      nutritionPlansByDate: AppState["nutritionPlansByDate"];
      signature: string;
      generatedAt: string;
    }
  | { type: "DEVICE"; patch: Partial<AppState["device"]> }
  | { type: "DEVICE_CONNECTED"; provider: string }
  | { type: "DEVICE_SYNCED"; at: string; activities: AppState["activities"] }
  | { type: "MESSAGE"; message: ConversationMessage }
  | { type: "START_DAILY_SESSION"; date: string }
  | { type: "CLEAR_HISTORY"; clearedAt: string }
  | { type: "CONFIRM_PLAN"; id: string }
  | {
      type: "APPLY_ADJUSTMENT";
      adjustment: PlanAdjustmentData;
      nutritionPlansByDate: AppState["nutritionPlansByDate"];
    }
  | { type: "TICK" }
  | { type: "REMINDER_TRIGGER" }
  | { type: "REMINDER_ACTION"; status: "acknowledged" | "delayed" | "skipped" }
  | { type: "FEEDBACK"; patch: Partial<AppState["feedback"]> }
  | { type: "REPORT"; patch: Partial<AppState["report"]> }
  | { type: "RESET" };

const tabForRoute = (route: RouteId): AppState["activeTab"] | undefined => {
  if (route === "HOME-01") return "home";
  if (route === "TRAIN-HUB") return "training";
  if (route === "NUT-01") return "nutrition";
  if (route === "PROFILE-01") return "profile";
  return undefined;
};

function reducer(state: AppState, action: Action): AppState {
  switch (action.type) {
    case "NAVIGATE": {
      const tab = action.tab ?? tabForRoute(action.route) ?? state.activeTab;
      const push =
        action.route !== state.route &&
        !["HOME-01", "TRAIN-HUB", "NUT-01", "PROFILE-01"].includes(
          action.route,
        );
      return {
        ...state,
        route: action.route,
        activeTab: tab,
        stack: push ? [...state.stack, state.route] : state.stack,
      };
    }
    case "BACK": {
      const stack = [...state.stack];
      const route =
        stack.pop() ??
        (
          {
            home: "HOME-01",
            training: "TRAIN-HUB",
            nutrition: "NUT-01",
            profile: "PROFILE-01",
          } as const
        )[state.activeTab];
      return { ...state, route, stack };
    }
    case "PATCH":
      return { ...state, ...action.patch };
    case "PROFILE": {
      const profile = { ...state.profile, ...action.patch };
      const unchanged =
        profileSignature(profile) === state.planProfileSignature;
      return {
        ...state,
        profile,
        planGenerationStatus: unchanged ? state.planGenerationStatus : "idle",
      };
    }
    case "PLAN_GENERATED":
      return {
        ...state,
        planItems: action.items,
        planProgram: action.program,
        planVersion: action.items[0]?.version ?? state.planVersion + 1,
        selectedDate: action.items[0]?.date ?? state.selectedDate,
        planGenerationStatus: "ready",
        planProfileSignature: action.signature,
        planGeneratedAt: action.generatedAt,
        nutritionPlan:
          action.nutritionPlansByDate[action.items[0]?.date ?? ""] ?? null,
        nutritionPlansByDate: action.nutritionPlansByDate,
        nutritionPlanStatus: "ready",
        completedNutritionNodes: [],
        completedNutritionNodesByDate: {},
        toast: "Agent 已生成训练计划与配套营养规划",
      };
    case "DEVICE":
      return { ...state, device: { ...state.device, ...action.patch } };
    case "DEVICE_CONNECTED": {
      const connectedProviders = Array.from(
        new Set([...state.device.connectedProviders, action.provider]),
      );
      return {
        ...state,
        device: {
          ...state.device,
          provider: action.provider,
          connectedProviders,
          status: "connected",
          authorizingProvider: "",
          lastSyncAt: "尚未同步",
          weather: null,
          sleepHours: null,
          hrvMs: null,
          restingHeartRate: null,
        },
        toast: `${action.provider} 已连接，可继续连接其他设备或立即同步`,
      };
    }
    case "DEVICE_SYNCED": {
      const syncSources = state.device.connectedProviders.map((provider) =>
        provider === "高驰" ? "高驰 Pace 3" : provider,
      );
      return {
        ...state,
        activities: [
          ...state.activities.filter(
            (existing) =>
              !action.activities.some(
                (incoming) =>
                  incoming.id === existing.id ||
                  (incoming.planItemId === existing.planItemId &&
                    incoming.source === existing.source),
              ),
          ),
          ...action.activities,
        ],
        planItems: state.planItems.map((item) =>
          action.activities.some((activity) => activity.planItemId === item.id)
            ? { ...item, status: "completed" as const }
            : item,
        ),
        device: {
          ...state.device,
          status: "synced",
          lastSyncAt: action.at,
          syncSources,
          weather: { temperature: 23, humidity: 62, condition: "多云" },
          sleepHours: 7.2,
          hrvMs: 58,
          restingHeartRate: 52,
        },
        recoveryUpdatedAt: action.at,
        toast: action.activities.length
          ? `设备数据已同步到首页、训练记录页与我的 · 新增 ${action.activities.length} 条训练记录`
          : "设备数据已同步；所选日期没有发现训练记录",
      };
    }
    case "MESSAGE":
      return {
        ...state,
        conversation: [...state.conversation, action.message],
        composerDraft: "",
      };
    case "START_DAILY_SESSION": {
      if (state.homeSessionDate === action.date) return state;
      const archive = state.conversation.length
        ? [
            ...state.conversationArchive,
            {
              id: `session-${state.homeSessionDate}`,
              date: state.homeSessionDate,
              title: "训练与恢复对话",
              messages: state.conversation,
            },
          ]
        : state.conversationArchive;
      return {
        ...state,
        homeSessionDate: action.date,
        conversationArchive: archive,
        conversation: createInitialState().conversation,
        composerDraft: "",
        analysisContext: null,
        suggestedQuestionsStatus: "idle",
        suggestedQuestions: [],
      };
    }
    case "CLEAR_HISTORY":
      return {
        ...state,
        conversation: [],
        conversationArchive: [],
        composerDraft: "",
        analysisContext: null,
        conversationResetAt: action.clearedAt,
        historyResetStatus: "idle",
        suggestedQuestions: [],
        suggestedQuestionsStatus: "idle",
        suggestedQuestionsUpdatedAt: "",
        toast: "全部历史对话已永久删除",
      };
    case "CONFIRM_PLAN":
      return {
        ...state,
        planItems: state.planItems.map((p) =>
          p.id === action.id ? { ...p, status: "confirmed" } : p,
        ),
        toast: "已确认该训练，并同步到训练中心与首页",
      };
    case "APPLY_ADJUSTMENT": {
      const version = state.planVersion + 1;
      const changedIds = new Set(action.adjustment.affected);
      const adjustedItems = action.adjustment.items.map((item) =>
        changedIds.has(item.id)
          ? {
              ...item,
              status: "adjusted" as const,
              changed: true,
              version,
            }
          : { ...item, version },
      );
      const selectedChanged = adjustedItems.find((item) =>
        changedIds.has(item.id) &&
        state.planItems.some(
          (current) =>
            current.id === item.id && current.date === state.selectedDate,
        ),
      );
      const firstWeek = adjustedItems.filter(
        (item) => (item.weekIndex ?? 1) === state.planProgram.currentWeek,
      );
      const affectedDates = new Set(
        action.adjustment.changes.flatMap((change) => [
          change.before.date,
          change.after.date,
        ]),
      );
      const completedNutritionNodesByDate = Object.fromEntries(
        Object.entries(state.completedNutritionNodesByDate).filter(
          ([date]) => !affectedDates.has(date),
        ),
      );
      return {
        ...state,
        planVersion: version,
        planItems: adjustedItems,
        selectedDate: selectedChanged?.date ?? state.selectedDate,
        trainingView: "plan",
        planProgram: {
          ...state.planProgram,
          weeklyLoad: firstWeek.reduce((sum, item) => sum + item.load, 0),
          basis: [
            ...state.planProgram.basis.filter(
              (item) => !item.startsWith("最近调整："),
            ),
            `最近调整：${action.adjustment.reason}`,
          ],
        },
        reminder: {
          ...state.reminder,
          triggerLabel: "第 40 分钟",
          amount: "能量胶 1 支 · 饮水 150 ml",
        },
        nutritionPlan:
          action.nutritionPlansByDate[
            selectedChanged?.date ?? state.selectedDate
          ] ?? null,
        nutritionPlansByDate: action.nutritionPlansByDate,
        nutritionPlanStatus: "ready",
        completedNutritionNodes:
          completedNutritionNodesByDate[iso(0)] ?? [],
        completedNutritionNodesByDate,
        toast: `计划 V${version} 已同步到 TRAIN-HUB 计划和记录视图`,
      };
    }
    case "TICK": {
      if (state.live.status !== "recording") return state;
      const elapsed = state.live.elapsed + 1;
      const activePlan =
        state.planItems.find((item) => item.status === "in_progress") ??
        state.planItems.find((item) => item.date === state.selectedDate) ??
        state.planItems[0];
      const current = resolveTrainingProgress(activePlan, state.live);
      const paceSeconds = paceSecondsForStep(current.step, activePlan.pace);
      const distanceIncrement =
        current.step.type === "stretch" ? 0 : 1 / paceSeconds;
      const distance = Number(
        (state.live.distance + distanceIncrement).toFixed(3),
      );
      const reachedTarget =
        current.step.target.metric === "time"
          ? elapsed - state.live.stepStartedAt >= current.step.target.value * 60
          : distance - state.live.stepStartedDistance >=
            current.step.target.value;
      const nextIndex =
        reachedTarget && current.index < current.steps.length - 1
          ? current.index + 1
          : current.index;
      const stepChanged = nextIndex !== current.index;
      const nextStep = current.steps[nextIndex];
      const targetHeartRate =
        nextStep.type === "interval"
          ? 158
          : nextStep.type === "tempo"
            ? 150
            : nextStep.type === "warmup" || nextStep.type === "cooldown"
              ? 136
              : 144;
      const heartRate =
        state.live.heartRate === targetHeartRate
          ? targetHeartRate
          : state.live.heartRate +
            (state.live.heartRate < targetHeartRate ? 1 : -1);
      return {
        ...state,
        live: {
          ...state.live,
          elapsed,
          distance,
          heartRate,
          energy: Number(
            (
              state.live.energy + (nextStep.type === "interval" ? 0.22 : 0.16)
            ).toFixed(1),
          ),
          currentStepIndex: nextIndex,
          stepStartedAt: stepChanged ? elapsed : state.live.stepStartedAt,
          stepStartedDistance: stepChanged
            ? state.live.distance
            : state.live.stepStartedDistance,
        },
        toast: stepChanged
          ? `进入${nextStep.title}${current.steps[nextIndex + 1] ? ` · 下一步 ${current.steps[nextIndex + 1].title}` : " · 最后一步"}`
          : state.toast,
      };
    }
    case "REMINDER_TRIGGER":
      return state.reminder.enabled
        ? {
            ...state,
            reminder: {
              ...state.reminder,
              status: "triggered",
              vibrationCount: 1,
            },
          }
        : state;
    case "REMINDER_ACTION": {
      const planDate = state.nutritionPlan?.planDate ?? state.selectedDate;
      const completedForDate =
        state.completedNutritionNodesByDate[planDate] ??
        (planDate === iso(0) ? state.completedNutritionNodes : []);
      const completedNutritionNodes =
        action.status === "acknowledged"
          ? Array.from(
              new Set([
                ...completedForDate,
                state.reminder.nodeId,
              ]),
            )
          : completedForDate;
      const nutritionPlan =
        action.status === "acknowledged" && state.nutritionPlan
          ? {
              ...state.nutritionPlan,
              nodes: state.nutritionPlan.nodes.map((node) =>
                node.id === state.reminder.nodeId
                  ? { ...node, completedAt: new Date().toISOString() }
                  : node,
              ),
            }
          : state.nutritionPlan;
      const totalNodes = nutritionPlan?.nodes.length ?? 0;
      const nutritionCompletion = totalNodes
        ? Math.round((completedNutritionNodes.length / totalNodes) * 100)
        : state.nutritionCompletion;
      return {
        ...state,
        reminder:
          action.status === "delayed"
            ? {
                ...state.reminder,
                status: "scheduled",
                lastAction: "delayed",
                triggerSecond: state.live.elapsed + 600,
                triggerLabel: "延后 10 分钟",
              }
            : {
                ...state.reminder,
                status: action.status,
                lastAction: action.status,
              },
        nutritionPlan,
        nutritionPlansByDate:
          nutritionPlan && action.status === "acknowledged"
            ? { ...state.nutritionPlansByDate, [planDate]: nutritionPlan }
            : state.nutritionPlansByDate,
        nutritionCompletion,
        completedNutritionNodes:
          planDate === iso(0)
            ? completedNutritionNodes
            : state.completedNutritionNodes,
        completedNutritionNodesByDate:
          action.status === "acknowledged"
            ? {
                ...state.completedNutritionNodesByDate,
                [planDate]: completedNutritionNodes,
              }
            : state.completedNutritionNodesByDate,
        toast:
          action.status === "acknowledged"
            ? "补给已记录，营养页与训练记录已同步"
            : action.status === "delayed"
              ? "已延后 10 分钟，不会重复震动"
              : "本次已跳过，训练后可补记",
      };
    }
    case "FEEDBACK":
      return { ...state, feedback: { ...state.feedback, ...action.patch } };
    case "REPORT":
      return { ...state, report: { ...state.report, ...action.patch } };
    case "RESET":
      return createInitialState();
    default:
      return state;
  }
}

interface AppContextValue {
  state: AppState;
  go: (route: RouteId, tab?: AppState["activeTab"]) => void;
  back: () => void;
  patch: (patch: Partial<AppState>) => void;
  profile: (patch: Partial<AppState["profile"]>) => void;
  device: (patch: Partial<AppState["device"]>) => void;
  confirmPlan: (id: string) => void;
  applyAdjustment: (
    request: string,
    adjustment?: PlanAdjustmentData,
  ) => Promise<boolean>;
  sendMessage: (text: string, displayText?: string) => Promise<void>;
  retryLastMessage: () => Promise<void>;
  generatePlan: () => Promise<boolean>;
  refreshSuggestedQuestions: () => Promise<void>;
  clearConversationHistory: () => Promise<boolean>;
  connectDevice: (provider: string) => Promise<void>;
  syncDevice: () => Promise<void>;
  tick: () => void;
  triggerReminder: () => void;
  reminderAction: (status: "acknowledged" | "delayed" | "skipped") => void;
  feedback: (patch: Partial<AppState["feedback"]>) => void;
  report: (patch: Partial<AppState["report"]>) => void;
  reset: () => void;
  openAnalysis: (title: string, sourceRoute: RouteId, summary: string) => void;
  sendAnalysisToHome: (
    title: string,
    sourceRoute: RouteId,
    summary: string,
    analysis?: TrainingAnalysisData,
  ) => void;
  publishTrainingAnalysisUpdate: (
    analysis: TrainingAnalysisData,
    previousCompletion: number,
    nextCompletion: number,
  ) => void;
  openNutritionFeedback: (sourceRoute: RouteId) => void;
  startDemo: (demo: number) => void;
}

const AppContext = createContext<AppContextValue | null>(null);

export function AppProvider({ children }: { children: ReactNode }) {
  const saved = (() => {
    try {
      return JSON.parse(
        localStorage.getItem("running-ai-prd-v1") ?? "null",
      ) as AppState | null;
    } catch {
      return null;
    }
  })();
  const defaults = createInitialState();
  const savedPlanItems = saved?.planItems ?? [];
  const migratedPlanItems =
    saved &&
    saved.planGenerationStatus === "ready" &&
    savedPlanItems.length > 0 &&
    savedPlanItems.length <= 7
      ? buildDemoCycle(
          savedPlanItems.map((item, index) => ({
            ...item,
            id: item.id.replace(/-w\d+$/, "") || `plan-${index + 1}`,
            weekIndex: 1,
          })),
          saved.planProgram?.totalWeeks ?? 8,
        )
      : savedPlanItems;
  const hydrated = saved
    ? {
        ...defaults,
        ...saved,
        authMode: localStorage.getItem("running-ai-access-token")
          ? ("authenticated" as const)
          : ("guest" as const),
        planItems: migratedPlanItems.length
          ? migratedPlanItems
          : defaults.planItems,
        planProgram: {
          ...defaults.planProgram,
          ...saved.planProgram,
          startDate:
            saved.planProgram?.startDate ??
            migratedPlanItems[0]?.date ??
            defaults.planProgram.startDate,
          endDate:
            saved.planProgram?.endDate ??
            migratedPlanItems.at(-1)?.date ??
            defaults.planProgram.endDate,
        },
        profile: { ...defaults.profile, ...saved.profile },
        device: {
          ...defaults.device,
          ...saved.device,
          connectedProviders:
            saved.device.connectedProviders ??
            (saved.device.provider ? [saved.device.provider] : []),
          syncSources:
            saved.device.syncSources ??
            (saved.device.provider ? [saved.device.provider] : []),
        },
        feedback: {
          ...defaults.feedback,
          ...saved.feedback,
          thirst: saved.feedback.thirst ?? 5,
        },
        mealEntries: saved.mealEntries ?? defaults.mealEntries,
        hydrationByDate:
          saved.hydrationByDate ?? {
            [iso(0)]: saved.hydrationMl ?? defaults.hydrationMl,
          },
        selectedMealId: saved.selectedMealId ?? "",
        nutritionPlansByDate: saved.nutritionPlansByDate ?? {},
        completedNutritionNodesByDate:
          saved.completedNutritionNodesByDate ?? {
            [iso(0)]: saved.completedNutritionNodes ?? defaults.completedNutritionNodes,
          },
      }
    : defaults;
  const [state, dispatch] = useReducer(reducer, hydrated);

  useEffect(() => {
    localStorage.setItem("running-ai-prd-v1", JSON.stringify(state));
  }, [state]);
  useEffect(() => {
    dispatch({ type: "START_DAILY_SESSION", date: iso(0) });
  }, []);
  useEffect(() => {
    if (
      !state.profileComplete ||
      state.planGenerationStatus !== "ready" ||
      state.nutritionPlanStatus !== "idle" ||
      !state.planItems.length
    ) {
      return;
    }
    dispatch({
      type: "PATCH",
      patch: { nutritionPlanStatus: "loading" },
    });
    const context = buildAgentContext(state);
    void Promise.all(
      state.planItems.map(async (item) => {
        const nutrition = await services.nutrition.buildPlan(
          item.id,
          context,
          item.date,
        );
        return [item.date, nutrition.data] as const;
      }),
    )
      .then((entries) => {
        const nutritionPlansByDate = Object.fromEntries(entries);
        dispatch({
          type: "PATCH",
          patch: {
            nutritionPlansByDate,
            nutritionPlan:
              nutritionPlansByDate[state.selectedDate] ?? entries[0]?.[1] ?? null,
            nutritionPlanStatus: "ready",
          },
        });
      })
      .catch(() => {
        dispatch({
          type: "PATCH",
          patch: {
            nutritionPlanStatus: "error",
            toast: "随训练计划生成的营养规划暂时无法读取",
          },
        });
      });
  }, [
    state.nutritionPlanStatus,
    state.planGenerationStatus,
    state.planProfileSignature,
    state.planVersion,
    state.profileComplete,
  ]);
  useEffect(() => {
    if (!state.toast) return;
    const id = window.setTimeout(
      () => dispatch({ type: "PATCH", patch: { toast: "" } }),
      2800,
    );
    return () => window.clearTimeout(id);
  }, [state.toast]);

  const runPlanGeneration = async () => {
    if (!state.profileComplete) {
      dispatch({ type: "PATCH", patch: { toast: "请先完成首次基础档案" } });
      return null;
    }
    dispatch({
      type: "PATCH",
      patch: { planGenerationStatus: "loading", agentFailure: false },
    });
    try {
      const context = buildAgentContext(state);
      const result = await services.agent.generatePlan(context);
      const expectedFrequency = Math.max(
        1,
        Math.min(7, state.profile.frequency),
      );
      const expectedCycleItems =
        expectedFrequency * result.data.program.totalWeeks;
      if (result.data.items.length !== expectedCycleItems) {
        throw new Error(
          `Agent 返回 ${result.data.items.length} 次训练，与 ${result.data.program.totalWeeks} 周 × 每周 ${expectedFrequency} 次不一致`,
        );
      }
      const nutritionContext = {
        ...context,
        plan: result.data.items,
      };
      const nutritionEntries = await Promise.all(
        result.data.items.map(async (item) => {
          const nutrition = await services.nutrition.buildPlan(
            item.id,
            nutritionContext,
            item.date,
          );
          return [item.date, nutrition.data] as const;
        }),
      );
      dispatch({
        type: "PLAN_GENERATED",
        items: result.data.items,
        program: result.data.program,
        nutritionPlansByDate: Object.fromEntries(nutritionEntries),
        signature: profileSignature(state.profile),
        generatedAt: `今天 ${timeNow()}`,
      });
      return result.data;
    } catch (error) {
      dispatch({
        type: "PATCH",
        patch: {
          planGenerationStatus: "error",
          agentFailure: true,
          toast:
            error instanceof Error
              ? error.message
              : "计划生成失败，请检查网络后重试",
        },
      });
      return null;
    }
  };

  const value = useMemo<AppContextValue>(
    () => ({
      state,
      go: (route, tab) => dispatch({ type: "NAVIGATE", route, tab }),
      back: () => dispatch({ type: "BACK" }),
      patch: (patch) => dispatch({ type: "PATCH", patch }),
      profile: (patch) => dispatch({ type: "PROFILE", patch }),
      device: (patch) => dispatch({ type: "DEVICE", patch }),
      confirmPlan: (id) => dispatch({ type: "CONFIRM_PLAN", id }),
      applyAdjustment: async (request, adjustment) => {
        dispatch({
          type: "PATCH",
          patch: { toast: "Agent 正在生成结构化变更…" },
        });
        try {
          const result = adjustment
            ? { data: adjustment }
            : await services.agent.adjustPlan(
                request,
                state.planItems,
                state.selectedDate,
              );
          const currentIds = new Set(state.planItems.map((item) => item.id));
          const resultIds = new Set(result.data.items.map((item) => item.id));
          if (
            result.data.items.length !== state.planItems.length ||
            resultIds.size !== currentIds.size ||
            [...currentIds].some((id) => !resultIds.has(id)) ||
            result.data.affected.some((id) => !currentIds.has(id))
          ) {
            throw new Error("Agent 返回的计划结构不完整");
          }
          const nutritionContext = {
            ...buildAgentContext(state),
            plan: result.data.items,
          };
          const affectedItems = result.data.items.filter((item) =>
            result.data.affected.includes(item.id),
          );
          const regeneratedNutrition = await Promise.all(
            affectedItems.map(async (item) => {
              const nutrition = await services.nutrition.buildPlan(
                item.id,
                nutritionContext,
                item.date,
              );
              return [item.date, nutrition.data] as const;
            }),
          );
          const nutritionPlansByDate = { ...state.nutritionPlansByDate };
          result.data.changes.forEach((change) => {
            if (change.before.date !== change.after.date) {
              delete nutritionPlansByDate[change.before.date];
            }
          });
          regeneratedNutrition.forEach(([date, plan]) => {
            nutritionPlansByDate[date] = plan;
          });
          dispatch({
            type: "APPLY_ADJUSTMENT",
            adjustment: result.data,
            nutritionPlansByDate,
          });
          return true;
        } catch {
          dispatch({
            type: "PATCH",
            patch: {
              agentFailure: true,
              toast: "计划调整失败，原计划已保留，请重新生成",
            },
          });
          return false;
        }
      },
      sendMessage: async (text, displayText) => {
        if (!text.trim()) return;
        dispatch({
          type: "MESSAGE",
          message: {
            id: crypto.randomUUID(),
            role: "user",
            text: displayText?.trim() || text,
            time: timeNow(),
          },
        });
        dispatch({ type: "PATCH", patch: { agentFailure: false } });
        if (state.safetyBlocked) {
          dispatch({
            type: "MESSAGE",
            message: {
              id: crypto.randomUUID(),
              role: "agent",
              text: "检测到高风险症状状态。请停止训练并尽快寻求医疗帮助；在解除安全状态前不再提供普通训练或补给建议。",
              time: timeNow(),
              card: "safety",
            },
          });
          return;
        }
        if (state.nutritionFeedbackPending.active) {
          const pending = state.nutritionFeedbackPending;
          const activity =
            state.activities.find((item) => item.id === pending.activityId) ??
            state.activities.find((item) => item.date === pending.planDate) ??
            null;
          const plan =
            state.planItems.find((item) => item.id === activity?.planItemId) ??
            state.planItems.find((item) => item.date === pending.planDate) ??
            null;
          const nutritionPlan =
            state.nutritionPlansByDate[pending.planDate] ?? state.nutritionPlan;
          const nodes = nutritionPlan?.nodes ?? [];
          const normalized = text.replace(/\s+/g, "");
          let nextCompletion: number | null = null;
          let completedNodeIds: string[] | null = null;
          const percentMatch = normalized.match(/(\d{1,3})(?:%|％|成)/);
          const countMatch = normalized.match(/(?:完成(?:了)?|补了)(\d+)项/);
          if (/(全部没|都没|没有完成|一项都没|完全没)/.test(normalized)) {
            nextCompletion = 0;
            completedNodeIds = [];
          } else if (/(全部完成|都完成|全都完成|按计划完成|都补了)/.test(normalized)) {
            nextCompletion = 100;
            completedNodeIds = nodes.map((node) => node.id);
          } else if (countMatch && nodes.length) {
            const count = Math.min(nodes.length, Number(countMatch[1]));
            completedNodeIds = nodes.slice(0, count).map((node) => node.id);
            nextCompletion = Math.round((count / nodes.length) * 100);
          } else if (percentMatch) {
            const raw = Number(percentMatch[1]);
            nextCompletion = normalized.includes("成") && raw <= 10 ? raw * 10 : Math.min(100, raw);
            const count = nodes.length ? Math.round((nextCompletion / 100) * nodes.length) : 0;
            completedNodeIds = nodes.slice(0, count).map((node) => node.id);
          } else if (nodes.length && /(训练前|训练中|训练后)/.test(normalized)) {
            const missedPhases = ["训练前", "训练中", "训练后"].filter(
              (phase) => new RegExp(`${phase}.{0,4}(没|未|忘|漏|跳过)`).test(normalized),
            );
            const positivePhases = ["训练前", "训练中", "训练后"].filter(
              (phase) => normalized.includes(phase) && !missedPhases.includes(phase),
            );
            completedNodeIds = missedPhases.length
              ? nodes.filter((node) => !missedPhases.includes(node.phase)).map((node) => node.id)
              : nodes.filter((node) => positivePhases.includes(node.phase)).map((node) => node.id);
            nextCompletion = Math.round((completedNodeIds.length / nodes.length) * 100);
          }
          if (nextCompletion === null || completedNodeIds === null || !activity || !plan || !nutritionPlan) {
            dispatch({
              type: "MESSAGE",
              message: {
                id: crypto.randomUUID(),
                role: "agent",
                text: "我还不能准确判断完成度。你可以直接说“全部完成”“完成了 3 项”“约 75%”，或说明训练前、训练中、训练后哪些环节没有完成。",
                time: timeNow(),
                contextVersion: `nutrition-feedback-clarify-${pending.activityId || pending.planDate}`,
              },
            });
            return;
          }
          const previousCompletion = activity.nutritionCompletion;
          const updatedActivity = { ...activity, nutritionCompletion: nextCompletion };
          const updatedNodes = nodes.map((node) => ({
            ...node,
            completedAt: completedNodeIds.includes(node.id) ? node.completedAt || new Date().toISOString() : undefined,
          }));
          const updatedPlan = { ...nutritionPlan, nodes: updatedNodes };
          dispatch({
            type: "PATCH",
            patch: {
              activities: state.activities.map((item) => item.id === activity.id ? updatedActivity : item),
              nutritionPlan: state.nutritionPlan?.planDate === pending.planDate ? updatedPlan : state.nutritionPlan,
              nutritionPlansByDate: { ...state.nutritionPlansByDate, [pending.planDate]: updatedPlan },
              completedNutritionNodes: pending.planDate === iso(0) ? completedNodeIds : state.completedNutritionNodes,
              completedNutritionNodesByDate: { ...state.completedNutritionNodesByDate, [pending.planDate]: completedNodeIds },
              nutritionCompletion: pending.planDate === iso(0) ? nextCompletion : state.nutritionCompletion,
              nutritionFeedbackPending: { ...pending, active: false },
            },
          });
          try {
            const analysisResult = await services.agent.analyzeTraining(
              plan,
              updatedActivity,
              updatedNodes,
              completedNodeIds,
              state.feedback,
            );
            dispatch({ type: "PATCH", patch: { trainingAnalysis: analysisResult.data } });
            dispatch({
              type: "MESSAGE",
              message: {
                id: crypto.randomUUID(),
                role: "agent",
                text: `收到，我已将本次补给完成度由 ${previousCompletion}% 更新为 ${nextCompletion}%，并结合你的补给反馈重新生成训练分析。${nextCompletion < 100 ? "未完成部分会纳入恢复和下一次补给建议。" : "本次补给执行完整，后续会继续观察耐受与恢复反馈。"}`,
                time: timeNow(),
                card: "training_performance",
                sources: ["用户对话反馈", "本次训练补给方案", "设备训练实绩"],
                contextVersion: analysisResult.data.contextVersion,
                analysis: analysisResult.data,
              },
            });
          } catch {
            dispatch({
              type: "MESSAGE",
              message: {
                id: crypto.randomUUID(),
                role: "agent",
                text: `补给完成度已保存为 ${nextCompletion}%，但训练分析暂时更新失败。原训练实绩和本次反馈都已保留，可以稍后让我重新分析。`,
                time: timeNow(),
              },
            });
          }
          return;
        }
        const asksForPlan =
          /(生成|制定|创建|安排|做).{0,8}(训练)?计划|训练计划.{0,8}(生成|制定|创建|安排|做)/.test(
            text,
          );
        if (asksForPlan && state.planGenerationStatus !== "ready") {
          const generated = await runPlanGeneration();
          if (generated)
            dispatch({
              type: "MESSAGE",
              message: {
                id: crypto.randomUUID(),
                role: "agent",
                text: `已根据你的完整档案生成「${generated.program.title}」，包含每周 ${generated.items.length} 次训练及对应营养规划。你可以前往训练中心确认，或继续告诉我需要调整的地方。`,
                time: timeNow(),
                card: "training",
                sources: generated.program.basis,
                contextVersion: generated.contextVersion,
              },
            });
          return;
        }
        try {
          const context = buildAgentContext(state);
          const result = await services.agent.chat(text, context);
          dispatch({
            type: "MESSAGE",
            message: {
              id: crypto.randomUUID(),
              role: "agent",
              text: result.data.answer,
              time: timeNow(),
              card: result.data.safety ? "safety" : undefined,
              sources: result.sourceData,
              contextVersion: result.data.contextVersion,
            },
          });
          if (result.data.safety)
            dispatch({ type: "PATCH", patch: { safetyBlocked: true } });
        } catch {
          dispatch({
            type: "PATCH",
            patch: {
              agentFailure: true,
              lastFailedPrompt: text,
              toast: "Agent 接口暂时无法访问，你的输入已保留",
            },
          });
        }
      },
      retryLastMessage: async () => {
        if (!state.lastFailedPrompt || state.agentRetrying) return;
        dispatch({
          type: "PATCH",
          patch: { agentRetrying: true, agentFailure: false },
        });
        try {
          const result = await services.agent.chat(
            state.lastFailedPrompt,
            buildAgentContext(state),
          );
          dispatch({
            type: "MESSAGE",
            message: {
              id: crypto.randomUUID(),
              role: "agent",
              text: result.data.answer,
              time: timeNow(),
              card: result.data.safety ? "safety" : undefined,
              sources: result.sourceData,
              contextVersion: result.data.contextVersion,
            },
          });
          dispatch({
            type: "PATCH",
            patch: {
              agentRetrying: false,
              lastFailedPrompt: "",
              toast: "Agent 已重新生成回答",
            },
          });
        } catch {
          dispatch({
            type: "PATCH",
            patch: {
              agentRetrying: false,
              agentFailure: true,
              toast: "重新请求仍未成功，请稍后再试",
            },
          });
        }
      },
      generatePlan: async () => {
        return Boolean(await runPlanGeneration());
      },
      refreshSuggestedQuestions: async () => {
        dispatch({
          type: "PATCH",
          patch: { suggestedQuestionsStatus: "loading" },
        });
        try {
          const result = await services.agent.suggestedQuestions(
            buildAgentContext(state),
          );
          dispatch({
            type: "PATCH",
            patch: {
              suggestedQuestions: result.data.questions,
              suggestedQuestionsStatus: "ready",
              suggestedQuestionsUpdatedAt: timeNow(),
            },
          });
        } catch {
          dispatch({
            type: "PATCH",
            patch: { suggestedQuestionsStatus: "error" },
          });
        }
      },
      clearConversationHistory: async () => {
        dispatch({
          type: "PATCH",
          patch: { historyResetStatus: "processing" },
        });
        try {
          const result = await services.agent.clearConversationHistory();
          dispatch({ type: "CLEAR_HISTORY", clearedAt: result.clearedAt });
          return true;
        } catch {
          dispatch({
            type: "PATCH",
            patch: {
              historyResetStatus: "error",
              toast: "历史对话删除失败，原记录已保留",
            },
          });
          return false;
        }
      },
      connectDevice: async (provider) => {
        dispatch({
          type: "DEVICE",
          patch: { status: "authorizing", authorizingProvider: provider },
        });
        try {
          await services.device.connect(provider);
          dispatch({ type: "DEVICE_CONNECTED", provider });
        } catch {
          dispatch({
            type: "DEVICE",
            patch: { status: "failed", authorizingProvider: "" },
          });
          dispatch({
            type: "PATCH",
            patch: { toast: `${provider} 授权失败，未写入设备列表，请重试` },
          });
        }
      },
      syncDevice: async () => {
        if (state.device.status === "disconnected") {
          dispatch({
            type: "PATCH",
            patch: { toast: "尚未连接设备，无法获取数据" },
          });
          return;
        }
        dispatch({ type: "DEVICE", patch: { status: "syncing" } });
        try {
          const result = await services.device.sync({
            provider: state.device.provider || state.device.connectedProviders[0] || "设备",
            selectedDate: state.selectedDate,
            plans: state.planItems,
          });
          dispatch({
            type: "DEVICE_SYNCED",
            at: `今天 ${timeNow()}`,
            activities: result.activities,
          });
        } catch {
          dispatch({ type: "DEVICE", patch: { status: "failed" } });
          dispatch({
            type: "PATCH",
            patch: { toast: "设备同步失败，已保留上次数据并标记为非最新" },
          });
        }
      },
      tick: () => dispatch({ type: "TICK" }),
      triggerReminder: () => dispatch({ type: "REMINDER_TRIGGER" }),
      reminderAction: (status) => dispatch({ type: "REMINDER_ACTION", status }),
      feedback: (patch) => dispatch({ type: "FEEDBACK", patch }),
      report: (patch) => dispatch({ type: "REPORT", patch }),
      reset: () => {
        localStorage.removeItem("running-ai-prd-v1");
        dispatch({ type: "RESET" });
      },
      openAnalysis: (title, sourceRoute, summary) => {
        dispatch({
          type: "PATCH",
          patch: { analysisContext: { title, sourceRoute, summary } },
        });
        dispatch({ type: "NAVIGATE", route: "AI-02" });
      },
      sendAnalysisToHome: (title, sourceRoute, summary, analysis) => {
        const isTrainingSummary = sourceRoute === "TRAIN-SUMMARY";
        dispatch({
          type: "MESSAGE",
          message: {
            id: crypto.randomUUID(),
            role: "user",
            text: `查看${title}`,
            time: timeNow(),
          },
        });
        dispatch({
          type: "MESSAGE",
          message: {
            id: crypto.randomUUID(),
            role: "agent",
            text: isTrainingSummary
              ? "本次训练的计划与设备实绩已经对齐，关键结果整理如下。"
              : `${title}（来源：${sourceRoute}）\n${summary}\n\n建议依据：个人档案、设备数据、天气、历史训练与当前计划版本。你可以在下方继续追问。`,
            time: timeNow(),
            card: isTrainingSummary ? "training_performance" : undefined,
            contextVersion: isTrainingSummary
              ? analysis?.contextVersion ??
                `training-feedback-${state.lastCompletedActivityId || state.selectedDate}`
              : undefined,
            analysis: isTrainingSummary ? analysis : undefined,
          },
        });
        if (isTrainingSummary && !state.feedback.submitted) {
          dispatch({
            type: "MESSAGE",
            message: {
              id: crypto.randomUUID(),
              role: "agent",
              text: "补充本次训练的主观感受后，我会把恢复、补给与后续训练建议一起重新校准。",
              time: timeNow(),
              card: "training_feedback_prompt",
              contextVersion: `training-feedback-${state.lastCompletedActivityId || state.selectedDate}`,
            },
          });
        }
        dispatch({ type: "NAVIGATE", route: "HOME-01", tab: "home" });
      },
      openNutritionFeedback: (sourceRoute) => {
        const activity =
          state.activities.find((item) => item.id === state.lastCompletedActivityId) ??
          state.activities.find((item) => item.date === state.selectedDate) ??
          state.activities.at(0) ??
          null;
        const planDate = activity?.date ?? state.selectedDate;
        dispatch({
          type: "PATCH",
          patch: {
            nutritionFeedbackPending: {
              active: true,
              sourceRoute,
              planDate,
              activityId: activity?.id ?? "",
            },
            composerDraft: "",
          },
        });
        dispatch({
          type: "MESSAGE",
          message: {
            id: crypto.randomUUID(),
            role: "user",
            text: "向 CPT 教练反馈补给完成情况",
            time: timeNow(),
          },
        });
        dispatch({
          type: "MESSAGE",
          message: {
            id: crypto.randomUUID(),
            role: "agent",
            text: "恭喜你完成今日训练，可以跟我反馈补给的完成情况。比如：全部完成、完成了 3 项、约 75%，或告诉我哪些环节没有完成。",
            time: timeNow(),
            sources: ["本次训练补给方案", "训练设备实绩", "用户自然语言反馈"],
            contextVersion: `nutrition-feedback-${activity?.id ?? planDate}`,
          },
        });
        dispatch({ type: "NAVIGATE", route: "HOME-01", tab: "home" });
      },
      publishTrainingAnalysisUpdate: (
        analysis,
        previousCompletion,
        nextCompletion,
      ) => {
        dispatch({
          type: "PATCH",
          patch: { trainingAnalysis: analysis },
        });
        dispatch({
          type: "MESSAGE",
          message: {
            id: crypto.randomUUID(),
            role: "agent",
            text: `补给情况已更新：本次训练补给完成度由 ${previousCompletion}% 更新为 ${nextCompletion}%。我已结合最新记录重新生成训练分析。`,
            time: timeNow(),
            card: "training_performance",
            sources: ["NUT-02 补给完成记录", "本次训练设备实绩", "训练计划"],
            contextVersion: analysis.contextVersion,
            analysis,
          },
        });
      },
      startDemo: (demo) => {
        const routeMap: Record<number, RouteId> = {
          1: "AUTH-01",
          2: "HOME-01",
          3: "TRAIN-HUB",
          4: "PROFILE-01",
          5: "TRAIN-READY",
          6: "NUT-01",
          7: "HOME-01",
          8: "STATE-LAB",
          9: "RECORD-06",
          10: "REPORT-01",
        };
        dispatch({ type: "RESET" });
        dispatch({
          type: "PATCH",
          patch:
            demo === 1
              ? { demo, profileComplete: false, planGenerationStatus: "idle" }
              : demo === 3
                ? {
                    demo,
                    trainingView: "plan" as const,
                    selectedDate: iso(0),
                  }
              : demo === 4
                ? {
                    demo,
                    device: {
                      ...createInitialState().device,
                      provider: "",
                      connectedProviders: [],
                      syncSources: [],
                      status: "disconnected",
                      lastSyncAt: "尚未同步",
                      gpsGranted: false,
                      notificationGranted: false,
                      authorizingProvider: "",
                      weather: null,
                      sleepHours: null,
                      hrvMs: null,
                      restingHeartRate: null,
                    },
                  }
              : { demo },
        });
        dispatch({ type: "NAVIGATE", route: routeMap[demo] ?? "HOME-01" });
      },
    }),
    [state],
  );

  return <AppContext.Provider value={value}>{children}</AppContext.Provider>;
}

export function useApp() {
  const ctx = useContext(AppContext);
  if (!ctx) throw new Error("useApp must be used inside AppProvider");
  return ctx;
}
