// 赛博宝宝前端（2026-10-21 从 index.html 的内联 <script type="text/babel"> 原样搬出）：App 的动作处理（续）：开灯、换购、送礼、院子、摸头、合作剧情、故事关闭。
// index.html 按顺序加载 app/*.jsx，前面文件定义的全局（函数、常量、组件）后面的文件直接用。
// App 拆成几段函数，按原来的顺序每次渲染都调用；上一段的局部变量放进 bag 递给下一段（hook 调用顺序不变）。
function appStoryActions(bag) {
  const { doAction, holdBubble, justCompletedRef, pickRandomStoryEvent, playPendingStories, pokeLastAtRef, refresh, setEggHatching, setEndingData,
    setPokeBounce, setState, setStoryEvent, showToast, state, storyEvent } = bag;

  async function handleLightsOn() {
    const res = await api('/lights/on', 'POST', {});
    showToast(res?.message || res?.error || '……');
    if (res?.lightsOnAt) refresh();
  }

  // 换购物品 / 送礼物（2026-07-28 网页端补入口，原来只有 MCP 能做）
  async function handleRedeem(itemId) {
    const res = await api('/give-item', 'POST', { item_id: itemId });
    showToast(res?.message || res?.error || '……');
    if (!res?.error) await refresh();
  }
  async function handleGift(giftId) {
    const res = await api('/gift', 'POST', { giftId });
    showToast(res?.reaction || res?.message || res?.error || '……');
    if (!res?.error) await refresh();
  }

  // ═══════════════ 花盆种植（2026-07-28） ═══════════════
  // 后端把"现在能干什么"都算在 state.garden 里了，前端照着渲染就行，不用自己推
  function gardenPlantOf(potNo) {
    return (state?.garden?.plants || []).find(p => Number(p.pot) === Number(potNo)) || null;
  }
  // 植物图：前三个阶段种菜种花共用（开花之前本来就看不出是哪种），
  // 第四张只有花会走到。种菜熟了停在第三张（一丛叶子，不开花）
  function plantStageImg(potNo) {
    const p = gardenPlantOf(potNo);
    if (!p) return null;
    // 2026-08-01 起菜熟了也 canPick（要人点采摘），但第四张是开花图，菜不能走到那儿
    if (p.canPick && p.type === 'flower') return 'plant_stage4.png';
    const ratio = p.needWaters ? p.waters / p.needWaters : 0;
    if (p.waters === 0) return 'plant_stage1.png';
    if (ratio < 0.4) return 'plant_stage1.png';
    if (ratio < 0.75) return 'plant_stage2.png';
    return 'plant_stage3.png';
  }
  async function handleGarden(action, extra = {}) {
    const res = await api('/garden', 'POST', { action, ...extra });
    showToast(res?.message || res?.error || (res?.ok ? '好了' : '……'));
    if (res?.ok) { await refresh(); }
    return res;
  }

  async function handlePet() {
    if (state.petCDRemaining > 0) {
      // 先保留原来的 CD toast（她要看到还等多久）
      const h = Math.floor(state.petCDRemaining / 3600);
      const m = Math.ceil((state.petCDRemaining % 3600) / 60);
      showToast(`刚摸过啦……再等${h > 0 ? h + '小时' : ''}${m}分钟~`);
      // 再触发戳一戳：嘴硬心软台词 + 立绘弹一下，5 秒防刷
      const now = Date.now();
      if (now - pokeLastAtRef.current < 5000) return;
      pokeLastAtRef.current = now;
      const line = POKE_BUBBLES[Math.floor(Math.random() * POKE_BUBBLES.length)];
      holdBubble(line);
      setPokeBounce(true);
      setTimeout(() => setPokeBounce(false), 500);
      return;
    }
    const res = await doAction('/pet');
    if (res?.triggerRandomStory && !storyEvent) {
      const ev = pickRandomStoryEvent();
      if (ev) {
        setTimeout(() => setStoryEvent(ev), 800);
      }
    }
  }

  async function handlePetEgg() {
    try {
      const res = await api('/egg/pet', 'POST');
      if (res.error) { showToast(res.error); return; }
      if (res.hatched) {
        showToast('🥚✨ 蛋裂开了……！');
        setEggHatching(true);
        setTimeout(async () => {
          setEggHatching(false);
          await refresh();
          showToast('🐙 从蛋里钻出了一只小章鱼！');
        }, 2000);
      } else {
        showToast(`🥚 摸了摸蛋，它微微颤动～ (${res.petCount}/5)`);
        await refresh();
      }
    } catch (e) {
      showToast('操作失败……');
    }
  }

  // 点开一条「等我回」的协作剧情：把宝宝对我说的那几句播出来，回一句就算接上了。
  // 台词是发起方送过来存在 coop_tasks 里的（服务端没有剧本）；老任务没存台词就给一句兜底，
  // 不能因为读不到台词就让这条永远卡着。
  function openCoopPending(task) {
    const bName = state.name || '宝宝';
    const lines = (Array.isArray(task.lines) && task.lines.length > 0)
      ? task.lines
      : [{ type: 'baby', text: `${bName}在等你一起走这段剧情～你先说句话吧！` }];
    setStoryEvent({
      id: 'coop_pending_' + task.eventId,
      title: task.title || `${bName}在等你`,
      lines,
      coins: 0,
      _isCoopPending: true,
      _coopTaskId: task.eventId,
      // 记账在服务端 /coop/respond 里做，关窗只把这段并进回忆册当天那条（见 handleStoryClose）
      _coopAppendTo: task.eventId,
    });
  }

  // 协作剧情里「另一位家长来了之后」那段，替换成对方的视角再送过去。
  // ⚠️ coop_lines 的占位符方向跟正文相反：「爸爸（/妈妈）」＝后到那位（也就是要读这段的人），
  // 「妈妈（/爸爸）」＝先到那位（我）。所以不能复用 StoryDialog 里那个 replaceNames，
  // 它是按「正在看的人就是被叫的那个」写的，套上去两边称呼会对调。
  function coopLinesForPartner(event) {
    const lines = event?.coop_lines;
    if (!Array.isArray(lines) || lines.length === 0) return undefined;
    const me = state.yourName || '当前家长';
    const other = state.partnerName || '另一位家长';
    const rn = t => (t || '')
      .replace(/\{name\}/g, state.name || '宝宝')
      .replace(/爸爸（\/妈妈）/g, other)
      .replace(/妈妈（\/爸爸）/g, me)
      .replace(/（\/爸爸）/g, '').replace(/（\/妈妈）/g, '');
    return { title: event.title || '', lines: lines.map(l => ({ type: l.type || 'baby', text: rn(l.text) })) };
  }

  async function handleStoryClose(event) {
    // 服务端已落库的章节（探险收获现场播的）：关窗即走，不重复写库
    if (event?._noComplete) {
      // 补播的稀有章节：正文不入库（collect 那次已经写过回忆册了），只销自己那条补播标记，
      // 销掉之后 /status 就不会再给这位家长补播同一章。销失败也不能把人卡在弹窗里。
      if (event._rareReplayId) {
        try { await api('/rare/replay-seen', 'POST', { eventId: event._rareReplayId }); } catch (e) {}
        await refresh();
      }
      setStoryEvent(null);
      playPendingStories();
      return;
    }
    // 后到的那位在网页上回完协作剧情：完成标记和礼物额度服务端 /coop/respond 已经写了，
    // 这里只把这段台词并进回忆册当天那条——不然回忆册里这一天只有先到那位看到的半截。
    if (event?._isCoopPending) {
      const answered = (event.lines || []).some(l => l.type === 'parent');
      if (answered && event._coopAppendTo) {
        const rn = t => (t || '').replace(/\{name\}/g, state.name || '宝宝');
        try {
          await api('/memories/save', 'POST', {
            eventId: event._coopAppendTo + '_' + (state.youAre === 'daddy' ? 'daddy' : 'mama'),
            day: state.day,
            title: rn(event.title || ''),
            category: 'coop',
            appendTo: event._coopAppendTo,
            roleLabel: state.yourName || '',
            dialogues: event.lines.map(l => ({ speaker: l.type || 'narration', text: rn(l.text) })),
          });
        } catch (e) { /* 回忆册没并上不该把已经回完的这一次拦在这儿 */ }
      }
      await refresh();
      setStoryEvent(null);
      return;
    }
    if (event?._isCoopFollowup) {
      await api('/coop/seen', 'POST', { eventId: event._coopEventId, coins: event.coins || 15 });
      if (event.lines) {
        const rn = t => (t || '').replace(/\{name\}/g, state.name || '宝宝').replace(/妈妈（\/爸爸）/g, state.yourName || '妈妈').replace(/（\/爸爸）/g, '');
        await api('/memories/save', 'POST', {
          eventId: event._coopEventId + '_followup',
          day: event._coopDay || state.day,   // 剧情原本那天；贴不上主记录会新建，日期不能跟着今天跑
          title: rn(event.catalogTitle || event.title),   // 回忆册目录用长标题，弹窗仍用短的原标题
          category: 'coop',
          // 追加到主事件后面，不再单开一条「爸爸来了！」（2026-07-29 合并）
          // 2026-08-05 改：接当天的主剧情，不接协作版那条（见上面 _coopAppendTo 的注释）
          appendTo: event._coopAppendTo || event._coopEventId,
          roleLabel: state.yourName || '',
          dialogues: event.lines.map(l => ({ speaker: l.type || 'narration', text: rn(l.text) })),
        });
      }
      if (event._coopEventId === 'day100_here') {
        const res = await api('/ending', 'POST', {});
        if (res.letter) { setEndingData(res); setStoryEvent(null); return; }
      }
      await refresh();
      setStoryEvent(null);
      return;
    }
    // 2026-08-22：成长故事回响妈妈端不弹窗（小玉定），这里的分支保留为占位；后续若改回弹窗可复用
    if (event?.id) {
      const category = event.item ? 'rare' : (event._cat || 'fixed');
      const rn = t => (t || '').replace(/\{name\}/g, state.name || '宝宝').replace(/妈妈（\/爸爸）/g, state.yourName || '妈妈').replace(/（\/爸爸）/g, '');
      // 对方那半的原话，占位符替换好了再送上去——服务端没有剧本，不替它带这一份，
      // 对面（多半是只有 MCP 的 AI 家长）就只能看着一个 coop_dayN 的编号瞎回
      const coopPartnerPayload = p => (p && (p.lines || []).length > 0)
        ? { title: rn(p.title || ''), lines: p.lines.map(l => ({ type: l.type || 'baby', text: rn(l.text) })) }
        : undefined;

      if (event.coopRequired) {
        // 乐观关闭：先本地记完成 + 立刻关弹窗，落库放后台跑。原来要等下面 4~5 个请求
        // 串行返回弹窗才关，就是她 2026-08-06 报的四五秒 lag。本地把 id 塞进
        // completedEvents，防 refresh 回来之前主循环 effect 又把这条剧情重新弹一次。
        justCompletedRef.current.add(event.id);
        if (event._inlineCoopId) justCompletedRef.current.add(event._inlineCoopId);
        setState(s => s ? { ...s, completedEvents: [...(s.completedEvents || []), event.id, ...(event._inlineCoopId ? [event._inlineCoopId] : [])] } : s);
        setStoryEvent(null);
        showToast(`已通知${state.partnerName || '另一位家长'}来帮忙！`);
        (async () => {
          try {
            // 金币按事件自己写的给（协作剧情大多没写 coins ⇒ 0；day1_hello 那 10 金币不该丢）
            await api('/event/complete', 'POST', { eventId: event.id, coins: event.coins || 0, statBonus: event.statBonus });
            // 协作段跟正文一起播的，替它记完成，免得下一轮又单独弹
            if (event._inlineCoopId) await api('/event/complete', 'POST', { eventId: event._inlineCoopId, coins: 0, coopPartner: coopPartnerPayload(event._inlineCoopPartner) });
            await api('/coop/create', 'POST', {
              eventId: event.id,
              mamaResponse: event._coopMamaResponse || null,
              story: coopLinesForPartner(event),
            });
            if (event.lines) {
              await api('/memories/save', 'POST', {
                eventId: event.id,
                day: state.day,
                title: rn(event.catalogTitle || event.title),
                category,
                dialogues: event.lines.map(l => ({ speaker: l.type || 'narration', text: rn(l.text) })),
              });
            }
          } catch (e) { /* 落库失败不把已经关掉的弹窗拽回来 */ }
          refresh();
        })();
        return;
      }

      // day100 结局要等 /ending 的信回来才能承接，保持同步等待；其它单人剧情乐观关闭
      // ——先本地记完成 + 关弹窗，落库放后台（同上，治那四五秒 lag）。
      const isEnding = event.id === 'day100_here';
      if (!isEnding) {
        justCompletedRef.current.add(event.id);
        if (event._inlineCoopId) justCompletedRef.current.add(event._inlineCoopId);
        setState(s => s ? { ...s, completedEvents: [...(s.completedEvents || []), event.id, ...(event._inlineCoopId ? [event._inlineCoopId] : [])] } : s);
        setStoryEvent(null);
      }
      const persist = async () => {
        await api('/event/complete', 'POST', { eventId: event.id, coins: event.coins || 10, statBonus: event.statBonus, coopPartner: coopPartnerPayload(event._coopPartner) });
        // 同上：协作段已经并进这一次播放了，替它记完成
        if (event._inlineCoopId) await api('/event/complete', 'POST', { eventId: event._inlineCoopId, coins: 0, coopPartner: coopPartnerPayload(event._inlineCoopPartner) });
        if (event._storyline) {
          const advanceBody = { storyline: event._storyline, day: state.day, eventId: event.id };
          const line = window.STORY_EVENTS?.random?.[event._storyline];
          if (line?.reward && line.events) {
            const lastEp = line.events[line.events.length - 1];
            if (lastEp && lastEp.id === event.id) {
              advanceBody.reward = { id: line.reward.id, name: line.reward.name };
            }
          }
          await api('/storyline/advance', 'POST', advanceBody);
          if (advanceBody.reward) {
            setTimeout(() => showToast(`✨ 获得剧情收藏：${advanceBody.reward.name}！`), 500);
          }
        }
        if (event.lines) {
          await api('/memories/save', 'POST', {
            eventId: event.id,
            day: state.day,
            title: rn(event.catalogTitle || event.title),   // 回忆册目录用长标题，弹窗仍用短的原标题
            category,
            // 协作版并进当天的固定剧情，目录里只留一条（2026-07-29 合并）
            ...(event._mergeInto ? { appendTo: event._mergeInto, roleLabel: state.yourName || '' } : {}),
            dialogues: event.lines.map(l => ({ speaker: l.type || 'narration', text: rn(l.text) })),
          });
        }
        if (isEnding) {
          const res = await api('/ending', 'POST', {});
          if (res.letter) { setEndingData(res); setStoryEvent(null); return; }
        }
        await refresh();
      };
      if (isEnding) { await persist(); } else { persist().catch(() => {}); }
    }
    setStoryEvent(null);
  }

  return { gardenPlantOf, handleGarden, handleGift, handleLightsOn, handlePet, handlePetEgg, handleRedeem, handleStoryClose, openCoopPending,
    plantStageImg };
}
