// 赛博宝宝前端（2026-10-21 从 index.html 的内联 <script type="text/babel"> 原样搬出）：建造、书房、日记（拍立得）、更多。
// index.html 按顺序加载 app/*.jsx，前面文件定义的全局（函数、常量、组件）后面的文件直接用。
function BuildPanelInner({ inventory, coins, day, youAre, yourName, partnerName, onBuild }) {
  const ASSETS = window.APP_CONFIG.ASSETS_BASE;
  const [slots, setSlots] = useState([null, null, null]);
  const [building, setBuilding] = useState(false);
  const [result, setResult] = useState(null);

  const ownedFurniture = new Set((inventory || []).filter(i => i.item_type === 'furniture').map(i => i.item_id));
  const potQty = (inventory || []).find(i => i.item_id === 'yard_pot')?.quantity || 0;
  // 轮子造过没有：手上还有轮子，或者已经拼成滑板车了（轮子会被吃掉，所以两处都得看）
  const wheelEverBuilt = (inventory || []).some(i => (i.item_id === 'wheel' || i.item_id === 'scooter') && Number(i.quantity) > 0);
  const matMap = {};
  (inventory || []).forEach(i => {
    // 种子存的是 material 类型（方便进背包），但它不进任何配方，别混进材料库让人往格子里放
    if (i.item_type === 'material' && i.item_id !== 'seed') matMap[i.item_id] = i.quantity;
  });

  const usedCounts = {};
  slots.forEach(s => { if (s) usedCounts[s] = (usedCounts[s] || 0) + 1; });

  function addToSlot(matId) {
    const available = (matMap[matId] || 0) - (usedCounts[matId] || 0);
    if (available <= 0) return;
    const idx = slots.indexOf(null);
    if (idx === -1) return;
    const next = [...slots];
    next[idx] = matId;
    setSlots(next);
  }

  function removeSlot(idx) {
    if (building) return;
    const next = [...slots];
    next[idx] = null;
    setSlots(next);
  }

  function findMatchingBuild() {
    const placed = slots.filter(Boolean).sort();
    if (placed.length === 0) return null;
    for (const [id, recipe] of Object.entries(BUILD_RECIPES)) {
      // 两段式滑板车的两道门（2026-08-03 小玉设计）
      // ⛔ 别改成"还差 X 天"的倒计时——她拍板过：没解锁的配方在玩家端就该完全不存在。
      // 轮子：Day 75 那天小璋画了张画才会做。
      if (id === 'wheel' && (day || 0) < 75) continue;
      // 镜子图纸只在自私期开始时开放，提前摆三枚贝壳也不能匹配。
      if (id === 'mirror' && (day || 0) < 55) continue;
      // 滑板车：只有爸爸端有这个配方，而且要等第一个轮子真被造出来。
      // 这道门是故意的——她要的是妈妈跟爸爸说起轮子，爸爸才发现自己多了个配方。
      if (id === 'scooter' && !(youAre === 'daddy' && wheelEverBuilt)) continue;
      const needed = [...recipe.mats].sort();
      if (needed.length === placed.length && needed.every((v, i) => v === placed[i])) {
        const info = FURNITURE_INFO[id] || YARD_FURNITURE_INFO[id];
        if (info?.consumable) return { id, owned: false, gold: recipe.gold };
        const ownedFlag = id === 'yard_pot' ? potQty >= 3 : ownedFurniture.has(id);
        if (ownedFlag) return { id, owned: true, gold: recipe.gold };
        return { id, owned: false, gold: recipe.gold };
      }
    }
    return null;
  }

  async function handleBuild() {
    const match = findMatchingBuild();
    if (!match) {
      setResult({ success: false, text: '这些材料组合不出任何家具……' });
      return;
    }
    if (match.owned) {
      setResult({ success: false, text: `${(FURNITURE_INFO[match.id] || YARD_FURNITURE_INFO[match.id])?.name || match.id}已经有了~` });
      return;
    }
    if (match.gold > 0 && (coins || 0) < match.gold) {
      setResult({ success: false, text: `金币不够！需要${match.gold}💰` });
      return;
    }
    setBuilding(true);
    setTimeout(async () => {
      const res = await onBuild(match.id);
      const fInfo = FURNITURE_INFO[match.id] || YARD_FURNITURE_INFO[match.id];
      if (res && res.error) {
        setResult({ success: false, text: res.error });
      } else if (res && res.craftHint) {
        // 第一次造出轮子：不报"做好了"，报小璋那句疑问（2026-08-03 小玉写的词）
        setResult({ success: true, text: `${res.craftHint.title}\n${res.craftHint.text}` });
      } else {
        const buffText = fInfo?.buff
          ? `\n✨ ${match.id === 'bookshelf'
            ? `${youAre === 'daddy' ? yourName : partnerName}每天讲故事，摸头剧情概率翻倍，持续三天`
            : renderFamilyText(fInfo.buff, '', yourName, partnerName)}`
          : '';
        setResult({ success: true, text: `${fInfo?.name || MATERIAL_NAMES[match.id] || match.id}做好了！${buffText}` });
      }
      setBuilding(false);
    }, 2000);
  }

  if (result) {
    return (
      <div style={{ textAlign: 'center', padding: '30px 10px' }}>
        {result.success ? <div style={{ fontSize: 48, marginBottom: 12 }}>🎉</div> : <img src={`${ASSETS}/items/cross.png`} style={{ width: 64, height: 64, objectFit: 'contain', marginBottom: 12 }} alt="" />}
        <div style={{ fontSize: 16, fontWeight: 'bold', color: '#6E5A4B', marginBottom: 20, whiteSpace: 'pre-line' }}>{result.text}</div>
        <button onClick={() => { setResult(null); setSlots([null, null, null]); }}
          style={{ padding: '10px 32px', background: 'linear-gradient(135deg, #D79B6D, #e0ac80)', color: 'white', border: 'none', borderRadius: 14, fontSize: 14, fontWeight: 'bold', cursor: 'pointer' }}>
          继续建造
        </button>
      </div>
    );
  }

  const hasMaterials = slots.some(Boolean);

  return (
    <>
      <div className="workshop-area">
        <img className="workshop-img" src={`${ASSETS}/UI/workshop.png`} alt="工作台" />
        <img className={`hammer-img${building ? ' hammer-building' : ''}`} src={`${ASSETS}/UI/hammer.png`} alt="锤子" />
      </div>

      <div className="cook-slots">
        {slots.map((s, i) => (
          <div key={i} className={`cook-slot${s ? ' filled' : ''}`} onClick={() => s && removeSlot(i)}>
            {s ? <>
              <img src={`${ASSETS}/materials/sprites/${s}.png`} style={{ width: 36, height: 36, objectFit: 'contain' }}
                onError={e => { e.target.style.display='none'; }} />
              <div style={{ fontSize: 9, color: '#6E5A4B', marginTop: 2 }}>{MATERIAL_NAMES[s]}</div>
            </> : <div style={{ fontSize: 20, color: '#D9C8B4' }}>+</div>}
          </div>
        ))}
      </div>

      <div className="cook-hint">配方可以问问{partnerName || '另一位家长'} 🐾</div>
      {(() => {
        const match = findMatchingBuild();
        if (match && !match.owned && match.gold > 0) {
          const canAfford = (coins || 0) >= match.gold;
          return <div style={{ textAlign: 'center', fontSize: 13, color: canAfford ? '#D79B6D' : '#e07a5f', marginBottom: 4 }}>
            需要 {match.gold}💰 {canAfford ? '' : '（金币不够）'}
          </div>;
        }
        return null;
      })()}
      <button onClick={handleBuild} disabled={!hasMaterials || building}
        style={{ display: 'block', margin: '8px auto', padding: '10px 40px', background: hasMaterials ? 'linear-gradient(135deg, #D79B6D, #e0ac80)' : '#E0D5C8', color: 'white', border: 'none', borderRadius: 14, fontSize: 15, fontWeight: 'bold', cursor: hasMaterials ? 'pointer' : 'default', opacity: hasMaterials ? 1 : 0.5 }}>
        {building ? '建造中……' : 'Build!'}
      </button>

      <div style={{ marginTop: 10 }}>
        <div className="inv-category">🪵 材料库</div>
        <div className="inventory-grid">
          {Object.entries(matMap).map(([id, qty]) => {
            const available = qty - (usedCounts[id] || 0);
            return (
              <div key={id} className={`inv-slot${available <= 0 ? ' used-up' : ''}`} onClick={() => available > 0 && addToSlot(id)}>
                <img src={`${ASSETS}/materials/sprites/${id}.png`} style={{ width: 32, height: 32, objectFit: 'contain' }}
                  onError={e => { e.target.style.display='none'; }} alt="" />
                <div className="inv-name">{MATERIAL_NAMES[id] || id}</div>
                {available > 0 && <span className="inv-qty">×{available}</span>}
              </div>
            );
          })}
        </div>
      </div>
    </>
  );
}

// ═══════════════ 书房面板（回忆+背包） ═══════════════
function StudyPanel({ inventory, gifts, petPoints, onClose, babyName, parentName, pushSupported, pushSubscribed, onSubscribePush, onUnsubscribePush, isDaddy, giftCredits, otherName, onRedeem, onGift }) {
  const [tab, setTab] = useState('backpack');
  const memorySelectedRef = useRef(null);
  const ASSETS = window.APP_CONFIG.ASSETS_BASE;
  const handleBack = () => {
    if (tab === 'memory' && memorySelectedRef.current) {
      memorySelectedRef.current();
    } else {
      onClose();
    }
  };
  return (
    <div className="panel slide-right" style={{ background: '#FDF6EE', display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
      <div style={{ position: 'relative', width: '100%', maxWidth: 420, aspectRatio: '941 / 1672', maxHeight: '100vh' }}>
        <div style={{ position: 'absolute', inset: 0, backgroundImage: `url(${ASSETS}/UI/study_frame_clear.png)`, backgroundSize: '100% 100%', backgroundRepeat: 'no-repeat', zIndex: 2, pointerEvents: 'none' }} />
        <button className="back-btn-wiggle" onClick={handleBack} style={{ position: 'absolute', bottom: '3%', left: '50%', background: 'transparent', border: 'none', cursor: 'pointer', zIndex: 5, padding: 0 }}>
          <img src={`${ASSETS}/UI/back_button.png`} style={{ width: 200, height: 'auto' }} alt="返回" />
        </button>
        <div style={{ position: 'absolute', top: '10%', left: '7%', right: '7%', bottom: '12%', overflowY: 'auto', padding: '0 2px 40px', zIndex: 1 }}>
        <div style={{ display: 'flex', gap: 4, marginBottom: 16, background: '#EEDFCC', borderRadius: 14, padding: 3 }}>
          {[['backpack', '🏪 储藏柜'], ['memory', '🎞 回忆'], ['diary', '📖 日记'], ['more', '🔔 更多']].map(([key, label]) => (
            <button key={key} onClick={() => setTab(key)}
              style={{ flex: 1, padding: '8px 8px', border: 'none', borderRadius: 12, cursor: 'pointer', fontSize: 12, transition: 'all 0.2s', position: 'relative',
                background: tab === key ? '#FFFDF9' : 'transparent', fontWeight: tab === key ? 'bold' : 'normal',
                color: tab === key ? '#6E5A4B' : '#A08D7D', boxShadow: tab === key ? '0 2px 8px rgba(110,90,75,0.1)' : 'none' }}>{label}
              {key === 'more' && isNew() && <span className="new-bubble" style={{ top: -3, left: 'auto', right: -2 }}>New</span>}</button>
          ))}
        </div>
        {tab === 'memory' ? <MemoryPanelInner babyName={babyName} parentName={parentName} partnerName={otherName} isDaddy={isDaddy} backRef={memorySelectedRef} /> : tab === 'diary' ? <DiaryPanelInner /> : tab === 'backpack' ? <BackpackPanelInner inventory={inventory} gifts={gifts} petPoints={petPoints} isDaddy={isDaddy} giftCredits={giftCredits} babyName={babyName} meName={parentName} otherName={otherName} onRedeem={onRedeem} onGift={onGift} /> : tab === 'more' ? <MorePanel pushSupported={pushSupported} pushSubscribed={pushSubscribed} onSubscribe={onSubscribePush} onUnsubscribe={onUnsubscribePush} /> : null}
        </div>
      </div>
    </div>
  );
}

// 📷 日记里斜贴的一张拍立得（diary_mock.png 的样子）：图有就用存好的，没有就拿 plan 现画（顺手补传）
function DiaryPolaroid({ p, onOpen }) {
  const [img, setImg] = useState(null);
  const [plan, setPlan] = useState(null);
  useEffect(() => {
    let alive = true;
    (async () => {
      const res = await api(`/adventure/photo?id=${p.adventureId}`);
      if (!alive || !res || res.error) return;
      setPlan(res.plan || null);
      if (res.image) { setImg(res.image); return; }
      if (res.plan && window.Polaroid) {
        const url = await window.Polaroid.cardDataUrl(res.plan, { px: 350 });
        if (!alive) return;
        setImg(url);
        if (!advPhotoTried.has(p.adventureId)) { advPhotoTried.add(p.adventureId); api('/adventure/photo', 'POST', { adventureId: p.adventureId, image: url }).catch(() => {}); }
      }
    })().catch(() => {});
    return () => { alive = false; };
  }, [p.adventureId]);
  return (
    <button type="button" onClick={() => img && onOpen({ img, plan, caption: p.caption })} aria-label={`拍立得：${p.caption}，点开看大图`}
      style={{ float: 'right', width: '48%', margin: '2px -8px 6px 10px', transform: 'rotate(4deg)', padding: 0, border: 'none', background: 'none', cursor: img ? 'zoom-in' : 'default', minHeight: img ? 0 : 120 }}>
      {img ? <img src={img} alt={p.caption} style={{ width: '100%', display: 'block' }} />
        : <div style={{ height: 150, borderRadius: 4, background: '#fbf7ec', boxShadow: '0 4px 10px rgba(70,45,25,.18)', display: 'flex', alignItems: 'center', justifyContent: 'center', color: '#C4A98A', fontSize: 11 }}>冲洗中……</div>}
    </button>
  );
}
// 点开看大图：屏幕上重新画一张高清的（同一个 plan 画出来是同一张），画好之前先放存着的那张
function PolaroidLightbox({ shot, onClose }) {
  const [hi, setHi] = useState(null);
  useEffect(() => {
    let alive = true;
    if (shot.plan && window.Polaroid) window.Polaroid.card(shot.plan, { px: 1000 }).then(c => { if (alive) setHi(c.toDataURL('image/png')); }).catch(() => {});
    const onKey = e => { if (e.key === 'Escape') onClose(); };
    window.addEventListener('keydown', onKey);
    return () => { alive = false; window.removeEventListener('keydown', onKey); };
  }, [shot]);
  // 挂到 body 上：日记面板外面那层有 transform，position:fixed 在里面会被框住
  return ReactDOM.createPortal(
    <div role="dialog" aria-label={`拍立得：${shot.caption}`} onClick={onClose}
      style={{ position: 'fixed', inset: 0, zIndex: 9000, background: 'rgba(60,42,30,.72)', display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center', padding: 16, animation: 'fadeIn .25s ease' }}>
      <img src={hi || shot.img} alt={shot.caption} style={{ width: 'min(92vw, 520px)', height: 'auto', imageRendering: 'auto' }} />
      <div style={{ color: '#fff7ec', fontSize: 12, marginTop: 6, opacity: 0.8 }}>点一下回到日记</div>
    </div>, document.body
  );
}

function DiaryPanelInner() {
  const [entries, setEntries] = useState(null);
  const [flowersByDay, setFlowersByDay] = useState({});
  const [photosByDay, setPhotosByDay] = useState({});
  const [bigShot, setBigShot] = useState(null);
  const ASSETS = window.APP_CONFIG.ASSETS_BASE;

  useEffect(() => {
    api('/diary').then(res => {
      setEntries(res.entries || []);
      setFlowersByDay(res.flowersByDay || {});
      setPhotosByDay(res.photosByDay || {});
    }).catch(() => setEntries([]));
  }, []);

  if (entries === null) return (
    <div style={{ textAlign: 'center', padding: 40 }}>
      <div className="action-spinner" style={{ margin: '0 auto 12px' }} />
      <div style={{ color: '#A08D7D', fontSize: 13 }}>加载日记中……</div>
    </div>
  );

  if (entries.length === 0) return <div style={{ textAlign: 'center', color: '#A08D7D', padding: 40 }}>日记还是空白的~</div>;

  return (
    <div>
      {entries.map((entry, i) => (
        <div key={i} style={{ marginBottom: 16, padding: '12px 14px', background: '#FFFDF9', borderRadius: 14, border: '1px solid #EEDFCC' }}>
          <div style={{ fontSize: 11, color: '#C4A98A', marginBottom: 4 }}>
            Day {entry.day}
            {(flowersByDay[entry.day] || []).map((f, j) => (
              <span key={j} style={{ marginLeft: 6, color: '#E8A0BF', verticalAlign: 'middle' }}>
                <img src={`${ASSETS}/yard_items/flower_icon_pink.png`} alt="花" style={{ width: 14, height: 14, verticalAlign: 'middle', marginRight: 2 }} />
                {f.from}送了花
              </span>
            ))}
          </div>
          {/* 📷 那天的拍立得：斜贴在正文右边，字绕着它走 */}
          {(photosByDay[entry.day] || []).slice(0, 1).map(p => <DiaryPolaroid key={p.adventureId} p={p} onOpen={setBigShot} />)}
          {/* pre-line：日记里的换行和空行是小璋分的段，不加这个全被压成一坨（2026-08-03 修，
              全 app 别处的多行文本本来就都写了 pre-line，只有日记这块漏了） */}
          <div style={{ fontSize: 13, color: '#6E5A4B', lineHeight: 1.6, whiteSpace: 'pre-line' }}>{entry.text}</div>
          {(photosByDay[entry.day] || []).length > 0 && <div style={{ clear: 'both' }} />}
        </div>
      ))}
      {bigShot && <PolaroidLightbox shot={bigShot} onClose={() => setBigShot(null)} />}
    </div>
  );
}

function MorePanel({ pushSupported, pushSubscribed, onSubscribe, onUnsubscribe }) {
  const notifySection = !pushSupported ? (
    <div style={{ textAlign: 'center', padding: '20px 0' }}>
      <div style={{ fontSize: 24, marginBottom: 8 }}>🔕</div>
      <div style={{ color: '#6E5A4B', fontSize: 13 }}>你的浏览器不支持推送通知</div>
      <div style={{ color: '#A08D7D', fontSize: 12, marginTop: 4 }}>试试用 Chrome 或 Safari 打开吧～</div>
    </div>
  ) : pushSubscribed ? (
    <div style={{ textAlign: 'center', padding: '20px 0' }}>
      <div style={{ fontSize: 24, marginBottom: 8 }}>🔔✅</div>
      <div style={{ color: '#6E5A4B', fontSize: 13 }}>通知已开启！</div>
      <div style={{ color: '#A08D7D', fontSize: 12, marginTop: 4 }}>宝宝闹脾气或探险回来时会推送通知给你～</div>
      <button onClick={onUnsubscribe}
        style={{ marginTop: 10, padding: '6px 18px', borderRadius: 10, border: '1px solid #E6D5C3', background: 'transparent', color: '#A08D7D', fontSize: 12, cursor: 'pointer' }}>
        关闭通知
      </button>
    </div>
  ) : (
    <div style={{ textAlign: 'center', padding: '20px 0' }}>
      <div style={{ fontSize: 24, marginBottom: 8 }}>🔔</div>
      <div style={{ color: '#6E5A4B', fontSize: 13, marginBottom: 12 }}>开启通知，宝宝闹脾气或探险回来时提醒你</div>
      <button onClick={onSubscribe}
        style={{ padding: '10px 28px', borderRadius: 12, border: 'none', background: 'linear-gradient(135deg, #D79B6D, #b8845a)', color: '#fff', fontSize: 14, cursor: 'pointer' }}>
        开启通知
      </button>
    </div>
  );
  return (
    <div>
      {notifySection}
      <div style={{ height: 1, background: '#EEDFCC', margin: '8px 0 16px' }} />
      <ShareInvite />
      <div style={{ height: 1, background: '#EEDFCC', margin: '16px 0' }} />
      <BugReportPanel />
    </div>
  );
}

