/* Reeve — IM surface. Chat list + per-thread conversation with Reeve.
   The email + the action are FUSED into one Feishu-style DecisionCard you act
   on in place. Sherrell is STAGED — each approve reveals the next step.
   Reuses ReeveMark / Phone / hiTime / HoldButton / Avatar / ProfileScreen. */
const { useState: useIM, useRef: useIMRef, useEffect: useIMEffect } = React;

/* ---------- slide-to-approve (replaces long-press; liquid-glass thumb) ---------- */
function SlideApprove({ label, onApprove }) {
  const trackRef = useIMRef(null);
  const dragRef = useIMRef({ active: false, startX: 0, max: 1, pid: null });
  const [x, setX] = useIM(0);
  const [done, setDone] = useIM(false);
  const [dragging, setDragging] = useIM(false);
  const THUMB = 38;

  const measure = () => {
    const t = trackRef.current;
    return t ? Math.max(1, t.clientWidth - THUMB - 8) : 1;
  };
  const onDown = (e) => {
    if (done) return;
    dragRef.current.active = true;
    dragRef.current.startX = e.clientX - x;
    dragRef.current.max = measure();
    dragRef.current.pid = e.pointerId;
    setDragging(true);
    try {trackRef.current.setPointerCapture(e.pointerId);} catch (err) {}
  };
  const finish = () => {
    setDone(true);
    setX(dragRef.current.max);
    const t = setTimeout(() => onApprove(), 300);
    return t;
  };
  const onMove = (e) => {
    const d = dragRef.current;
    if (!d.active) return;
    let nx = e.clientX - d.startX;
    nx = Math.max(0, Math.min(nx, d.max));
    setX(nx);
    if (nx >= d.max - 0.5) {d.active = false;setDragging(false);finish();}
  };
  const onUp = () => {
    const d = dragRef.current;
    if (!d.active) return;
    d.active = false;
    setDragging(false);
    if (!done) setX(0);
  };

  const pct = x / dragRef.current.max;
  return (
    <div className={`slide ${done ? "done" : ""} ${dragging ? "dragging" : ""}`} ref={trackRef}
    onPointerDown={onDown} onPointerMove={onMove} onPointerUp={onUp} onPointerCancel={onUp}>
      <span className="slide-fill" style={{ width: x + THUMB + 4 + "px" }} />
      <span className="slide-label" style={{ opacity: Math.max(0, 1 - pct * 1.8) }}>
        <span className="slide-label-tx">{label}</span>
      </span>
      <span className="slide-donetx" style={{ opacity: done ? 1 : 0 }}>已批准 · 发送中</span>
      <span className={`slide-thumb ${done ? "ok" : ""}`} style={{ transform: `translateX(${x}px)` }}>
        <span className="slide-thumb-ic">{done ?
          <svg viewBox="0 0 24 24" width="17" height="17" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round"><path d="m4 12.5 5 5 11-11" /></svg> :
          <svg viewBox="0 0 24 24" width="17" height="17" fill="none" stroke="currentColor" strokeWidth="2.2" strokeLinecap="round" strokeLinejoin="round"><path d="M5 12h12M12 6l6 6-6 6" /></svg>}</span>
      </span>
    </div>);

}

/* ---------- plain message pieces ---------- */
function ReeveBubble({ m, cont }) {
  return (
    <div className={`im-msg reeve ${cont ? "cont" : ""} im-enter`}>
      <span className="im-msg-av"><ReeveMark size={15} /></span>
      <div className="im-bubble">{hiTime(m.text)}</div>
    </div>);

}
function MeBubble({ m }) {
  return (
    <div className="im-msg me im-enter">
      <span className="im-msg-av"><Avatar /></span>
      <div className="im-bubble">
        {m.voice ? <span className="im-voicetag"><i><b /><b /><b /></i>语音</span> : null}
        {hiTime(m.text)}
      </div>
    </div>);

}
function SysLine({ m }) {
  return <div className="im-sysline im-enter">{m.text}</div>;
}

/* an actual email shown in the thread (e.g. the prior chase emails that got no
   reply) — a real mail block with timestamp, not a chat bubble. */
function EmailMsg({ m }) {
  return (
    <div className="im-mailmsg im-enter">
      <div className={`im-mail ${m.dir === "out" ? "out" : "in"}`}>
        <div className="im-mail-h">
          <span className="im-mail-dir">{m.dir === "out" ? "你发出" : "收到"}</span>
          <b className="im-mail-ppl">{m.from} → {m.to}</b>
          <span className="tm">{m.time}</span>
        </div>
        {m.subject ? <div className="im-mail-subj">主题 · {m.subject}</div> : null}
        <div className="im-mail-body">{hiTime(m.body)}</div>
        {m.noreply ? <div className="im-mail-noreply"><i className="nr-dot" />{m.noreply}</div> : null}
      </div>
    </div>);

}

/* ---------- the fused decision card (email + reasoning + draft + decide here) ---------- */
function IncomingBlock({ inc, dim }) {
  return (
    <div className={`im-c-inc ${dim ? "dim" : ""}`}>
      <span className="im-c-inc-av">{(inc.from || "?").slice(0, 1)}</span>
      <div className="im-c-inc-tx">
        <div className="im-c-inc-h"><span className="em-ic">原邮件</span><b>{inc.from}</b><span className="tm">{inc.time}</span></div>
        {inc.subject ? <div className="im-c-inc-subj">主题 · {inc.subject}</div> : null}
        <div className="im-c-inc-body">{inc.body}</div>
      </div>
    </div>);

}

function DecisionCard({ m, cont, chips, specOn, onApprove, onChip, onEdit }) {
  const live = !m.sent && !m.replaced;
  const assisted = specOn && m.assist;
  return (
    <div className={`im-cardmsg ${cont ? "cont" : ""} im-enter`}>
      <span className="im-cardmsg-av"><ReeveMark size={15} /></span>
      <div className={`im-card ${live ? "live" : "done"} ${m.replaced ? "replaced" : ""}`}>
        <div className="im-card-head">
          <span className="im-card-kicker"><ReeveMark size={12} />{live ? "待你拍板" : m.replaced ? "已替换" : "已发出"}</span>
          <span className="im-card-title">{m.head}</span>
        </div>

        {assisted ?
        <div className="im-card-assist">
            <span className="im-card-assist-tag"><SpecMark size={12} stroke={2.1} />保险专家协办</span>
            {m.assistNote ? <span className="im-card-assist-note">{hiTime(m.assistNote)}</span> : null}
          </div> :
        null}

        {m.incoming ? <IncomingBlock inc={m.incoming} dim={!live} /> : null}

        {live && m.why ?
        <ul className="im-card-why">{m.why.map((w, i) => <li key={i}>{hiTime(w)}</li>)}</ul> :
        null}

        <div className={`im-card-draft ${live ? "" : "dim"}`}>
          <div className="im-card-draft-tag"><span>Reeve 拟的回复 · 你可改</span>{live ? <button className="edit" onClick={() => onEdit(m.draft)}>改这封</button> : null}</div>
          {hiTime(m.draft)}
        </div>

        {m.sent ?
        <div className="im-card-foot sent"><span className="tick">✓</span>已发送 · {m.verb}{m.sentNote ? <span className="note"> · {m.sentNote}</span> : null}</div> :
        m.replaced ?
        <div className="im-card-foot rep">已改为下面的新版本 ↓</div> :

        <div className="im-card-act">
            <SlideApprove label="滑动批准" onApprove={onApprove} />
            {chips && chips.length ?
          <div className="im-card-chips">
                {chips.map((c, i) => <button className="im-card-chip" key={i} onClick={() => onChip(c)}>{c}</button>)}
              </div> :
          null}
          </div>
        }
      </div>
    </div>);

}

function Typing() {
  return (
    <div className="im-typing">
      <span className="im-typing-av"><ReeveMark size={15} /></span>
      <div className="im-typing-dots"><i /><i /><i /></div>
    </div>);

}

/* ---------- one conversation (renders only messages[0..cut]) ---------- */
function rawFor(id) {
  return id === "sherrell" ? window.EMBER.thread && window.EMBER.thread.raw :
  id === "marcus" ? window.EMBER.thread2 && window.EMBER.thread2.raw :
  id === "comerford" ? window.EMBER.threadComerford && window.EMBER.threadComerford.raw :
  id === "adam" ? window.EMBER.threadAdam && window.EMBER.threadAdam.raw : null;
}

function Conversation({ conv, typing, onBack, onApprove, onSend, onInfo, onEditDraft, onRename }) {
  const [text, setText] = useIM("");
  const [editing, setEditing] = useIM(false);
  const [editSheet, setEditSheet] = useIM(null);
  const [draftT, setDraftT] = useIM("");
  const scrollRef = useIMRef(null);
  const [specOn] = useSpecialist();
  const title = conv.title || conv.name;
  const startRename = () => {setDraftT(title);setEditing(true);};
  const commitRename = () => {const v = draftT.trim();if (v && v !== title && onRename) onRename(conv.id, v);setEditing(false);};
  const visible = conv.messages.slice(0, conv.cut);
  useIMEffect(() => {
    const el = scrollRef.current;
    if (el) el.scrollTop = el.scrollHeight;
  }, [conv.cut, visible.length, typing]);

  const submit = () => {const v = text.trim();if (!v) return;setText("");onSend(conv.id, v);};
  const onKey = (e) => {if (e.key === "Enter") {e.preventDefault();submit();}};

  const hasPending = visible.some((m) => m.t === "card" && !m.sent && !m.replaced);
  const more = conv.cut < conv.messages.length;
  const raw = rawFor(conv.id);
  let prevReeve = false;

  return (
    <React.Fragment>
      <div className={`im-cnav ${conv.status === "watch" ? "watch" : ""}`}>
        <button className="im-back" onClick={onBack} aria-label="返回">‹</button>
        <span className="im-cnav-av" style={AV_TINT[conv.id] ? { "--pav": AV_TINT[conv.id] } : null}>{conv.avatar}</span>
        <div className="im-cnav-tx">
          {editing ?
          <input className="im-cnav-rename" autoFocus value={draftT}
          onChange={(e) => setDraftT(e.target.value)}
          onKeyDown={(e) => {if (e.key === "Enter") {e.preventDefault();commitRename();}if (e.key === "Escape") setEditing(false);}}
          onBlur={commitRename} /> :
          <button className="im-cnav-name as-btn" onClick={startRename} title="重命名">
            <span className="ttl">{title}</span>
            <span className="rn-ic" aria-hidden="true">✎</span>
          </button>}
          <div className="im-cnav-sub"><i />{editing ? "重命名 · 回车保存" : conv.status === "watch" ? "Reeve 盯着这条线" : "Reeve 跟进中"}</div>
        </div>
        {raw ? <button className="im-cnav-info" onClick={() => onInfo(conv.id)} aria-label="原邮件">原文</button> : null}
      </div>

      <div className="im-thread" ref={scrollRef}>
        <div className="im-thread-lead">
          <span className="im-thread-lead-av"><ReeveMark size={19} stroke={1.9} /></span>
          <span className="im-thread-lead-name">Reeve</span>
        </div>
        {visible.map((m, i) => {
          if (m.t === "sys") {prevReeve = false;return <SysLine m={m} key={i} />;}
          if (m.t === "mail") {prevReeve = false;return <EmailMsg m={m} key={i} />;}
          if (m.t === "me") {prevReeve = false;return <MeBubble m={m} key={i} />;}
          if (m.t === "card") {
            const cont = prevReeve;prevReeve = true;
            return <DecisionCard m={m} key={i} cont={cont} chips={conv.chips} specOn={specOn}
            onApprove={() => onApprove(conv.id, i)} onChip={(c) => onSend(conv.id, c)} onEdit={(draft) => setEditSheet({ idx: i, draft, head: m.head })} />;
          }
          const cont = prevReeve;prevReeve = true;
          return <ReeveBubble m={m} key={i} cont={cont} />;
        })}
        {typing ? <Typing /> : null}
      </div>

      <div className="im-foot">
        <div className="im-composer">
          <input className="im-input" value={text} placeholder={window.EMBER.im.composerHint}
          onChange={(e) => setText(e.target.value)} onKeyDown={onKey} />
          <span className="im-mic" aria-hidden="true"><i /><i /><i /></span>
          <button className={`im-send ${text.trim() ? "" : "dim"}`} onClick={submit} aria-label="发送">↑</button>
        </div>
      </div>

      {editSheet ?
      <DraftEditSheet data={editSheet} name={conv.name}
        onClose={() => setEditSheet(null)}
        onApply={(t, send) => { onEditDraft(conv.id, editSheet.idx, t); if (send) onApprove(conv.id, editSheet.idx); setEditSheet(null); }} /> :
      null}
    </React.Fragment>);

}

/* ---------- chat list (home) — clean IM list (WhatsApp conventions):
   avatar · name + time · preview + unread pill. Preview is plain gray. ---------- */
const AV_TINT = {
  sherrell: "oklch(58% .13 168)", // teal-green
  marcus: "oklch(56% .14 255)", // blue
  comerford: "oklch(52% .13 28)", // commercial — deep brick
  adam: "oklch(56% .12 290)" // retention — indigo-violet
};
function ListRow({ conv, onOpen, onStop, openId, setOpenId }) {
  const ACTION_W = 92;
  const drag = useIMRef({ active: false, startX: 0, base: 0, moved: false });
  const [dx, setDx] = useIM(0);
  const [dragging, setDragging] = useIM(false);
  const isOpen = openId === conv.id;
  useIMEffect(() => {if (!isOpen && dx !== 0) setDx(0);}, [isOpen]);

  const down = (e) => {drag.current = { active: true, startX: e.clientX, base: dx, moved: false };setDragging(true);};
  const move = (e) => {
    const d = drag.current;if (!d.active) return;
    let nx = d.base + (e.clientX - d.startX);
    nx = Math.max(-ACTION_W, Math.min(0, nx));
    if (Math.abs(e.clientX - d.startX) > 4) d.moved = true;
    setDx(nx);
  };
  const up = () => {
    const d = drag.current;if (!d.active) return;
    d.active = false;setDragging(false);
    if (dx < -ACTION_W / 2) {setDx(-ACTION_W);setOpenId(conv.id);} else
    {setDx(0);if (isOpen) setOpenId(null);}
  };
  const click = (e) => {
    if (drag.current.moved) {e.preventDefault();return;}
    if (dx < 0) {setDx(0);setOpenId(null);return;}
    onOpen(conv.id);
  };

  return (
    <div className="im-swipe">
      <button className="im-swipe-action" onClick={() => {onStop(conv);}} tabIndex={isOpen ? 0 : -1}>删除</button>
      <button className={`im-row ${conv.status} ${conv.badge ? "unread" : ""} ${dragging ? "dragging" : ""}`}
      style={{ transform: `translateX(${dx}px)` }} onClick={click}
      onPointerDown={down} onPointerMove={move} onPointerUp={up} onPointerCancel={up}>
        <span className="im-av-wrap">
          <span className="im-av" style={AV_TINT[conv.id] ? { "--pav": AV_TINT[conv.id] } : null}>{conv.avatar}</span>
          <span className={`im-av-reeve ${conv.status === "watch" ? "watch" : ""}`} title="Reeve 跟进中"><ReeveMark size={12} stroke={2.6} /></span>
        </span>
        <span className="im-row-tx">
          <span className="im-row-top">
            <span className="im-row-name">{conv.title || conv.name}</span>
            <span className="im-row-time">{conv.time}</span>
          </span>
          <span className="im-row-prev">
            <span className="pv">{conv.preview}</span>
            {conv.badge ? <span className="im-badge">{conv.badge}</span> : null}
          </span>
          {conv.urgency ?
          <span className="im-row-sum" data-lv={conv.urgency.level}>
            <span className="im-sum-dot" />
            <span className="im-sum-tx">{conv.urgency.text}</span>
          </span> : null}
        </span>
      </button>
    </div>);

}

function ChatList({ conversations, onOpen, onStop }) {
  const [openId, setOpenId] = useIM(null);
  return (
    <React.Fragment>
      <div className="im-search-wrap"><IMSearch placeholder="搜索消息及会话" /></div>
      <div className="im-list has-tabbar">
        {conversations.map((c) => <ListRow conv={c} key={c.id} onOpen={onOpen} onStop={onStop} openId={openId} setOpenId={setOpenId} />)}
      </div>
    </React.Fragment>);

}

/* ---------- conversation management: stop-following (delete) with 2-step confirm ---------- */
function ConvActionSheet({ conv, onStop, onClose }) {
  return (
    <div className="im-mask" onClick={onClose}>
      <div className="im-asheet" onClick={(e) => e.stopPropagation()}>
        <div className="im-asheet-head"><b>{conv.name}</b><span>{conv.status === "watch" ? "Reeve 盯着这条线" : "Reeve 替你跟进"}</span></div>
        <button className="im-asheet-item danger" onClick={onStop}>停止跟进这条线</button>
        <button className="im-asheet-cancel" onClick={onClose}>取消</button>
      </div>
    </div>);

}
function ConvStopConfirm({ conv, onConfirm, onClose }) {
  return (
    <div className="im-mask center" onClick={onClose}>
      <div className="im-confirm" onClick={(e) => e.stopPropagation()}>
        <div className="im-confirm-t">删除 {conv.name} 这条会话？</div>
        <div className="im-confirm-b">会从会话列表移除，Reeve 不再跟进此 thread。记录仍保留 —— 原邮件留在邮箱里，你随时能重新开始。</div>
        <div className="im-confirm-acts">
          <button className="im-confirm-btn cancel" onClick={onClose}>取消</button>
          <button className="im-confirm-btn danger" onClick={onConfirm}>删除</button>
        </div>
      </div>
    </div>);

}

/* ---------- 草稿编辑半屏 sheet：在会话内直接改 Reeve 的草稿，不再跳邮箱 ---------- */
function DraftEditSheet({ data, name, onClose, onApply }) {
  const [text, setText] = useIM(data.draft || "");
  const dirty = text.trim() !== (data.draft || "").trim();
  return (
    <div className="ds-mask" onClick={onClose}>
      <div className="ds-sheet" onClick={(e) => e.stopPropagation()}>
        <div className="ds-grab" />
        <div className="ds-head">
          <div className="ds-head-tx">
            <span className="ds-title">编辑草稿</span>
            <span className="ds-sub">{data.head || "Reeve 拟的回复"} · 发给 {name}</span>
          </div>
          <button className="ds-x" onClick={onClose} aria-label="关闭">✕</button>
        </div>
        <div className="ds-tag"><ReeveMark size={12} /> Reeve 拟的回复 · 你可直接改</div>
        <textarea className="ds-field" value={text} autoFocus spellCheck={false}
          onChange={(e) => setText(e.target.value)} />
        <div className="ds-foot">
          <button className="ds-save" onClick={() => onApply(text, false)}>{dirty ? "存草稿" : "关闭"}</button>
          <button className="ds-send" onClick={() => onApply(text, true)} aria-label="用这版发送">用这版发送 <span className="ds-send-ic">↑</span></button>
        </div>
      </div>
    </div>);

}

/* ---------- reveal helpers ---------- */
function firstPendingIdx(msgs) {
  for (let i = 0; i < msgs.length; i++) if (msgs[i].t === "card" && !msgs[i].sent && !msgs[i].replaced) return i;
  return -1;
}
function cutFor(msgs) {const i = firstPendingIdx(msgs);return i < 0 ? msgs.length : i + 1;}

function initConvos() {
  const cs = JSON.parse(JSON.stringify(window.EMBER.im.conversations));
  cs.forEach((c) => {c.cut = cutFor(c.messages);});
  return cs;
}

/* ---------- scripted reply engine ---------- */
function matchScript(conv, input) {
  const lc = input.toLowerCase();
  return (conv.scripts || []).find((s) => s.match.some((k) => lc.includes(k.toLowerCase())));
}

/* ---------- the interactive app (tabs ⇄ conversation detail) ---------- */
function ReeveIMApp() {
  const [convos, setConvos] = useIM(initConvos);
  const [tab, setTab] = useIM("messages");
  const [view, setView] = useIM(null);
  const [entered, setEntered] = useIM(false);
  const [typingId, setTypingId] = useIM(null);
  const [confirmConv, setConfirmConv] = useIM(null);
  const [toast, setToast] = useIM(null);
  const [sheet, setSheet] = useIM(null);
  const [mailOpen, setMailOpen] = useIM(null); // mail id to open in 邮箱
  const [mailReply, setMailReply] = useIM(""); // reply text prefilled in 邮箱
  const [newOpen, setNewOpen] = useIM(false); // 新建会话 sheet（含附件/语音）
  const [drawer, setDrawer] = useIM(false); // 左侧抽屉
  const timers = useIMRef([]);

  useIMEffect(() => () => timers.current.forEach(clearTimeout), []);
  useIMEffect(() => {
    if (view) {const id = requestAnimationFrame(() => setEntered(true));return () => cancelAnimationFrame(id);}
    setEntered(false);
  }, [view]);

  const open = (id) => setView(id);
  const close = () => {setEntered(false);const t = setTimeout(() => setView(null), 330);timers.current.push(t);};
  const update = (id, fn) => setConvos((cs) => cs.map((c) => c.id === id ? fn({ ...c, messages: c.messages.slice() }) : c));
  const renameConv = (id, title) => update(id, (c) => {c.title = title;return c;});
  const editDraftText = (id, idx, txt) => update(id, (c) => {c.messages = c.messages.map((m, i) => i === idx ? { ...m, draft: txt } : m);return c;});

  /* approve the card at idx → reveal the next step */
  const approve = (id, idx) => {
    update(id, (c) => {
      c.messages = c.messages.map((m, i) => i === idx ? { ...m, sent: true, pending: false } : m);
      const next = firstPendingIdx(c.messages);
      c.cut = next < 0 ? c.messages.length : next + 1;
      if (next < 0) {
        c.status = "watch";c.badge = 0;
        c.preview = "已替你发出 · Reeve 盯着回复中";
      } else {
        c.preview = "已发一步 · 下一步草稿待你拍板";
      }
      return c;
    });
  };

  /* user replies (typed or via a card chip) → Reeve answers, maybe revises the draft */
  const send = (id, input) => {
    update(id, (c) => {
      const at = c.cut;
      c.messages.splice(at, 0, { t: "me", text: input });
      c.cut = at + 1;
      return c;
    });
    setTypingId(id);
    const conv = convos.find((c) => c.id === id);
    const hit = matchScript(conv, input);
    const t = setTimeout(() => {
      setTypingId(null);
      update(id, (c) => {
        let at = c.cut;
        c.messages.splice(at, 0, { t: "reeve", text: hit ? hit.reeve : c.scriptDefault || "收到。" });
        c.cut = ++at;
        if (hit && hit.card) {
          const pend = firstPendingIdx(c.messages);
          const head = hit.card.head || pend >= 0 && c.messages[pend].head || "修订版回复";
          if (pend >= 0) c.messages[pend] = { ...c.messages[pend], replaced: true, sent: false };
          c.messages.splice(at, 0, { t: "card", pending: true, head, why: hit.card.why, draft: hit.card.draft, verb: hit.card.verb });
          c.cut = at + 1;
        }
        return c;
      });
    }, 950 + Math.min(input.length * 14, 700));
    timers.current.push(t);
  };

  const openChat = (cid) => {setTab("messages");open(cid);};
  /* 「改这封」→ 跳到邮箱 + 打开原 thread + 把草稿 copy 进可编辑的回复框 */
  const editInMail = (cid, draft) => {
    setMailReply(draft || "");
    setMailOpen(cid);
    setTab("mail");
    close();
  };
  const cur = view && view !== "profile" ? convos.find((c) => c.id === view) : null;
  const sheetRaw = sheet ? rawFor(sheet) : null;
  const badge = convos.filter((c) => c.status === "act" && c.badge).length || null;
  const mailUnread = (window.mailData ? window.mailData().inbox.filter((m) => m.unread).length : 0) || null;
  const goMe = () => {setDrawer(false);setTab("me");};
  const navTo = (t) => {setDrawer(false);setTab(t);};

  /* stop-following (delete) with two-step confirm */
  const stopFollow = (id) => {
    setConvos((cs) => cs.filter((c) => c.id !== id));
    setConfirmConv(null);
    setToast("已删除 · Reeve 不再跟进此 thread");
    const t = setTimeout(() => setToast(null), 2400);timers.current.push(t);
  };

  /* 主动新建会话 — hand Reeve a task (text / voice / 附件); it picks it up */
  const startThread = (payload) => {
    setNewOpen(false);
    setTab("messages");
    const voice = payload && payload.voice;
    const hasAtt = payload && payload.atts && payload.atts.length;
    const note = voice ? "语音已转交 Reeve" : hasAtt ? "已交给 Reeve · 含附件" : "已交给 Reeve";
    setToast(note + " · 正在为你跟进，有进展就叫你");
    const t = setTimeout(() => setToast(null), 2600);timers.current.push(t);
  };

  return (
    <React.Fragment>
      <div className={`push-base ${view ? "pushed" : ""}`}>
        <div className="im-app">
          {tab === "messages" ?
          <div className="im-tabscreen">
              <IMTopBar title="消息" onMenu={() => setDrawer(true)} onMe={goMe} />
              <ChatList conversations={convos} onOpen={open} onStop={(c) => setConfirmConv(c)} />
              <HomeComposer onSubmit={(t) => startThread({ text: t })} onExpand={() => setNewOpen(true)} />
            </div> :
          null}
          {tab === "mail" ? <MailboxScreen onMenu={() => setDrawer(true)} onMe={goMe} onOpenChat={openChat} initialOpen={mailOpen} prefillReply={mailReply} onConsumeOpen={() => {setMailOpen(null);setMailReply("");}} /> : null}
          {tab === "wiki" ? <WikiScreen onMenu={() => setDrawer(true)} onMe={goMe} /> : null}
          {tab === "me" ? <div className="im-tabscreen"><ProfileScreen onBack={() => setTab("messages")} /></div> : null}
        </div>
      </div>
      <IMDrawer open={drawer} tab={tab} onClose={() => setDrawer(false)} onNav={navTo} onMe={goMe}
      badge={badge} mailUnread={mailUnread} count={convos.length} />
      {cur ?
      <div className={`push-over ${entered ? "in" : ""}`}>
          <Conversation conv={cur} typing={typingId === cur.id} onBack={close}
        onApprove={approve} onSend={send} onInfo={(id) => setSheet(id)} onEditDraft={editDraftText} onRename={renameConv} />
        </div> :
      null}
      {sheetRaw ? <MailSheet open={!!sheet} onClose={() => setSheet(null)} raw={sheetRaw} /> : null}
      {confirmConv ? <ConvStopConfirm conv={confirmConv} onConfirm={() => stopFollow(confirmConv.id)} onClose={() => setConfirmConv(null)} /> : null}
      {newOpen ? <DarkComposer onClose={() => setNewOpen(false)} onSubmit={startThread} /> : null}
      {toast ? <div className="im-toast">{toast}</div> : null}
    </React.Fragment>);

}

Object.assign(window, {
  ReeveBubble, MeBubble, SysLine, IncomingBlock, DecisionCard, Typing,
  Conversation, ListRow, ChatList, ConvActionSheet, ConvStopConfirm, DraftEditSheet, ReeveIMApp
});