// ═══════════════════════════════════════════════════════════════
// cm_line.jsx — LINE 官方帳號收件匣
// 讀 inbox_messages（live，非快照）。客人傳到 LINE@ 的訊息經
// line-inbound-webhook 即時存入,這裡顯示、複製、設預設回覆、擬回覆草稿。
// 依賴 ui.jsx(Icon) / cm_ui.jsx(PageHead, EmptyState, StatusPill) / consoleStore
// ═══════════════════════════════════════════════════════════════

// ⭐ 此功能只用在老闆自己的海邊小巫，固定鎖這家店（不開放多餐廳）。
const LINE_RESTAURANT_ID = "5fa79171-c0ff-400e-8a67-a7efb9b15c08";
const LINE_RESTAURANT_NAME = "海邊小巫";

function timeAgo(iso) {
  if (!iso) return "";
  const d = new Date(iso), now = new Date();
  const m = Math.floor((now - d) / 60000);
  if (m < 1) return "剛剛";
  if (m < 60) return m + " 分鐘前";
  const h = Math.floor(m / 60);
  if (h < 24) return h + " 小時前";
  const days = Math.floor(h / 24);
  if (days < 7) return days + " 天前";
  return (d.getMonth() + 1) + "/" + d.getDate();
}

const LINE_STATUS_META = {
  new:        { label: "新訊息", color: "#06c755", soft: "#06c75518" },
  needs_reply:{ label: "待回覆", color: "#e8a33d", soft: "#e8a33d18" },
  calendared: { label: "已排行事曆", color: "#3d7ee8", soft: "#3d7ee818" },
  replied:    { label: "已回覆", color: "#7a7f87", soft: "#7a7f8718" },
  archived:   { label: "已封存", color: "#aab0b8", soft: "#aab0b818" },
  ignored:    { label: "已略過", color: "#aab0b8", soft: "#aab0b818" },
};

function ConfirmReservationForm({ msg, parse, onCancel, onConfirm }) {
  const { useState } = React;
  const p = parse || {};
  const [name, setName] = useState(p.name || (msg.customer_display_name || "").replace(/[（(].*?[）)]/g, "").trim());
  const [phone, setPhone] = useState(p.phone || "");
  const [guest, setGuest] = useState(p.party_size || "");
  const [date, setDate] = useState(p.date || "");
  const [time, setTime] = useState(p.time || "");
  const [note, setNote] = useState("");
  const [busy, setBusy] = useState(false);
  const ready = name.trim() && /^0\d{8,9}$/.test(phone.trim()) && Number(guest) > 0 && /^\d{4}-\d{2}-\d{2}$/.test(date) && /^\d{1,2}:\d{2}$/.test(time);
  const fs = { width: "100%", padding: 8, borderRadius: 8, border: "1px solid var(--line)", fontSize: 14, marginTop: 4, boxSizing: "border-box" };
  const lb = { fontSize: 12, color: "var(--ink-2)" };
  return (
    <div style={{ marginTop: 10, border: "1px solid #06c75555", borderRadius: 10, padding: 14, background: "#06c7550a" }}>
      <strong style={{ fontSize: 13.5, color: "#06c755" }}><Icon name="calendar" size={14} /> 確認接單 — 寫進 ChefsMate 正式訂位</strong>
      <div style={{ fontSize: 12, color: "var(--ink-3)", margin: "4px 0 10px" }}>核對資料（LINE 沒有電話，請補上），按「寫入」才會建立正式訂位（自動確認、POS 看得到）。</div>
      <div style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: 10 }}>
        <label style={lb}>姓名<input value={name} onChange={(e) => setName(e.target.value)} style={fs} /></label>
        <label style={lb}>電話（必填）<input value={phone} onChange={(e) => setPhone(e.target.value)} placeholder="09xxxxxxxx" style={fs} /></label>
        <label style={lb}>日期<input value={date} onChange={(e) => setDate(e.target.value)} placeholder="2026-06-03" style={fs} /></label>
        <label style={lb}>時間<input value={time} onChange={(e) => setTime(e.target.value)} placeholder="19:00" style={fs} /></label>
        <label style={lb}>人數<input value={guest} onChange={(e) => setGuest(e.target.value)} type="number" style={fs} /></label>
        <label style={lb}>備註<input value={note} onChange={(e) => setNote(e.target.value)} style={fs} /></label>
      </div>
      {p.notes ? <div style={{ fontSize: 11.5, color: "#e8a33d", marginTop: 8 }}>⚠️ {p.notes}</div> : null}
      <div style={{ display: "flex", gap: 8, justifyContent: "flex-end", marginTop: 12 }}>
        <button className="btn btn-ghost" onClick={onCancel}>取消</button>
        <button className="btn btn-primary" style={{ background: "#06c755", borderColor: "#06c755" }} disabled={!ready || busy}
          onClick={async () => { setBusy(true); const ok = await onConfirm(msg, { customer_name: name.trim(), customer_phone: phone.trim(), guest_count: Number(guest), reservation_date: date, reservation_time: time, customer_note: note.trim() || null }); setBusy(false); if (ok) onCancel(); }}>
          {busy ? "寫入中…" : "寫入 ChefsMate"}
        </button>
      </div>
    </div>
  );
}

// ── 每位客人一個對話視窗：訂位資訊+按鈕釘在最上面，下面是聊天記錄 ──
function ConversationCard({ msgs, templates, draftsByMsg, repliesByUser, onStatus, onCopy, onSendReply, onConfirm, onQueueReply, onRegenerate }) {
  const { useState } = React;
  const sorted = [...msgs].sort((a, b) => new Date(a.received_at) - new Date(b.received_at));
  const latest = sorted[sorted.length - 1];
  const lineUserId = latest.line_user_id;
  // 該客人所有「訂位」訊息；活躍訂位＝最近一筆尚未接單的訂位
  const resMsgs = sorted.filter((m) => m.reservation_intent && m.parse_result && m.parse_result.date);
  const unreserved = resMsgs.filter((m) => !m.chefsmate_reservation_id);
  const activeRes = unreserved.length ? unreserved[unreserved.length - 1] : (resMsgs.length ? resMsgs[resMsgs.length - 1] : null);
  const replyTarget = activeRes || latest; // 回覆綁到活躍訂位訊息(或最新訊息)
  const parse = activeRes ? activeRes.parse_result : null;
  const reserved = activeRes ? !!activeRes.chefsmate_reservation_id : false;
  const canConfirm = activeRes && activeRes.reservation_intent && !reserved;
  const pendingReply = draftsByMsg[replyTarget.id] || null;
  // AI 分流結果（非訂位訊息）：取最近一筆有 ai_result 的
  const aiMsg = sorted.filter((m) => m.ai_result).slice(-1)[0];
  const ai = aiMsg ? aiMsg.ai_result : null;

  const [open, setOpen] = useState(false);
  const [draft, setDraft] = useState(pendingReply ? (pendingReply.reply_text || "") : "");
  const [sending, setSending] = useState(false);
  const [confirmOpen, setConfirmOpen] = useState(false);
  const [showAll, setShowAll] = useState(false);

  function applyTemplate(t) {
    const nm = (parse && parse.name) || latest.customer_display_name || "";
    const body = (t.body || "").replace(/\{name\}/g, nm).replace(/\{guest_name\}/g, nm);
    setDraft(draft ? draft + "\n" + body : body);
  }

  // 聊天時間軸：客人訊息(左) + 已送出回覆(右)，按時間排序
  const sentReplies = (repliesByUser[lineUserId] || []).filter((r) => r.send_status === "sent");
  const timeline = [
    ...sorted.map((m) => ({ t: new Date(m.received_at).getTime(), side: "in", text: m.message_type === "text" ? m.message_text : "[" + m.message_type + " 訊息]", key: "m" + m.id })),
    ...sentReplies.map((r) => ({ t: new Date(r.sent_at || r.created_at).getTime(), side: "out", text: r.reply_text, key: "r" + r.id })),
  ].sort((a, b) => a.t - b.t);
  const shown = showAll ? timeline : timeline.slice(-6);

  return (
    <div className="card" style={{ padding: 0, overflow: "hidden", marginBottom: 12 }}>
      {/* ── 頂部：客人 + 訂位摘要 + 按鈕 ── */}
      <div style={{ display: "flex", gap: 12, padding: 14 }}>
        <div style={{ width: 40, height: 40, borderRadius: "50%", flexShrink: 0, background: "#06c75522", color: "#06c755", display: "grid", placeItems: "center", overflow: "hidden" }}>
          {latest.customer_picture_url ? <img src={latest.customer_picture_url} alt="" style={{ width: "100%", height: "100%", objectFit: "cover" }} /> : <Icon name="user" size={20} />}
        </div>
        <div style={{ flex: 1, minWidth: 0 }}>
          <div style={{ display: "flex", alignItems: "center", gap: 8, flexWrap: "wrap" }}>
            <strong style={{ fontSize: 14 }}>{latest.customer_display_name || "（未知客人）"}</strong>
            <span className="chip" style={{ background: "var(--surface-2)", color: "var(--ink-3)", fontSize: 11 }}>{timeline.length} 則</span>
            {activeRes && !reserved && <span className="chip" style={{ background: "#3d7ee818", color: "#3d7ee8", fontSize: 11 }}><Icon name="calendar" size={11} />訂位待處理</span>}
            {reserved && <span className="chip" style={{ background: "#06c75518", color: "#06c755", fontSize: 11 }}><Icon name="check" size={11} />已接單</span>}
            {!reserved && parse && parse.hold_expires_at && <span className="chip" style={{ background: "#3d7ee818", color: "#3d7ee8", fontSize: 11 }} title="已自動暫留座位，逾時自動釋放"><Icon name="clock" size={11} />已暫留至 {(() => { try { return new Date(parse.hold_expires_at).toLocaleTimeString("zh-TW", { hour: "2-digit", minute: "2-digit", hour12: false }); } catch (e) { return ""; } })()}</span>}
            {!reserved && parse && parse.hold_blocked && <span className="chip" style={{ background: "#e8a33d18", color: "#e8a33d", fontSize: 11 }} title={"未自動暫留：" + parse.hold_blocked}><Icon name="alert" size={11} />未暫留</span>}
            {parse && parse.duplicate && <span className="chip" style={{ background: "#ef444418", color: "#ef4444", fontSize: 11 }} title={parse.duplicate.detail}><Icon name="alert" size={11} />可能重複</span>}
            {!activeRes && ai && ai.needs_human && <span className="chip" style={{ background: "#e8a33d18", color: "#e8a33d", fontSize: 11 }} title="關鍵字與 AI 都無法處理，需要你親自回覆"><Icon name="alert" size={11} />需真人回覆</span>}
            {!activeRes && ai && !ai.needs_human && ai.suggested_reply && <span className="chip" style={{ background: "#7c5cff18", color: "#7c5cff", fontSize: 11 }} title={ai.source === "faq" ? "命中問答庫，已擬好建議回覆" : "AI 已擬好建議回覆"}>🤖 {ai.source === "faq" ? "問答庫已擬覆" : "AI 已擬覆"}</span>}
            <span style={{ marginLeft: "auto", fontSize: 12, color: "var(--ink-3)" }}>{timeAgo(latest.received_at)}</span>
          </div>

          {/* 訂位摘要(釘頂) */}
          {parse && (parse.date || parse.time || parse.party_size) && (
            <div style={{ marginTop: 8, fontSize: 12.5, color: "var(--ink-2)", background: "var(--surface-2)", borderRadius: 8, padding: "6px 10px", display: "flex", gap: 12, flexWrap: "wrap" }}>
              {parse.date && <span><Icon name="calendar" size={11} /> {parse.date}</span>}
              {parse.time && <span><Icon name="clock" size={11} /> {parse.time}</span>}
              {parse.party_size && <span><Icon name="user" size={11} /> {parse.party_size} 位</span>}
              {parse.name && <span>姓名：{parse.name}</span>}
              {parse.phone && <span>電話：{parse.phone}</span>}
              {parse.availability && <span style={{ color: parse.verdict && parse.verdict.indexOf("ok") === 0 ? "#06c755" : (parse.verdict === "past_time" || parse.verdict === "full" || parse.verdict === "slot_full" ? "#e8a33d" : "var(--ink-2)") }}>判斷：{String(parse.availability).replace(/（剩\s*\d+\s*位）/, "")}</span>}
            </div>
          )}
          {parse && parse.duplicate && (
            <div style={{ marginTop: 8, fontSize: 12.5, color: "#ef4444", background: "#ef44440d", border: "1px solid #ef444433", borderRadius: 8, padding: "8px 10px" }}>
              <Icon name="alert" size={12} /> <strong>可能是重複訂位</strong>：{parse.duplicate.detail}。請確認是否同一筆。
            </div>
          )}

          {/* 按鈕(釘頂) */}
          <div style={{ marginTop: 10, display: "flex", gap: 8, flexWrap: "wrap" }}>
            {canConfirm && <button className="btn btn-primary" style={{ fontSize: 12.5, background: "#06c755", borderColor: "#06c755" }} onClick={() => setConfirmOpen(!confirmOpen)}><Icon name="calendar" size={13} /> 確認接單</button>}
            <button className="btn btn-ghost" style={{ fontSize: 12.5 }} onClick={() => setOpen(!open)}><Icon name="send" size={13} /> {pendingReply ? "回覆（已備草稿）" : "回覆"}</button>
            {latest.status !== "archived" && <button className="btn btn-ghost" style={{ fontSize: 12.5, color: "var(--ink-3)" }} onClick={() => sorted.forEach((m) => onStatus(m.id, "archived"))}>封存對話</button>}
          </div>
          {confirmOpen && <ConfirmReservationForm msg={activeRes} parse={parse} onCancel={() => setConfirmOpen(false)} onConfirm={onConfirm} />}
        </div>
      </div>

      {/* ── 回覆編輯區 ── */}
      {open && (
        <div style={{ borderTop: "1px solid var(--line)", padding: 14, background: "var(--surface-2)" }}>
          {templates.length > 0 && (
            <div style={{ display: "flex", gap: 6, flexWrap: "wrap", marginBottom: 8 }}>
              <span style={{ fontSize: 12, color: "var(--ink-3)", alignSelf: "center" }}>預設回覆：</span>
              {templates.map((t) => <button key={t.id} className="chip" style={{ cursor: "pointer", background: "var(--surface)", border: "1px solid var(--line)" }} onClick={() => applyTemplate(t)}>{t.label}</button>)}
            </div>
          )}
          <textarea value={draft} onChange={(e) => setDraft(e.target.value)} rows={3} placeholder="AI 會自動擬好草稿，可直接送或修改…"
            style={{ width: "100%", borderRadius: 8, border: "1px solid var(--line)", padding: 10, fontSize: 14, resize: "vertical", fontFamily: "inherit", boxSizing: "border-box" }} />
          {onRegenerate && <div style={{ fontSize: 11.5, color: "var(--ink-3)", marginTop: 6, lineHeight: 1.5 }}>
            💡 如果你改動了營業時間/菜單等資訊、讓 AI 的回答過時了，可以點下面的「重新產生（最新資料）」用最新資料更新 AI 的答案。
          </div>}
          <div style={{ display: "flex", gap: 8, marginTop: 8, justifyContent: "flex-end", alignItems: "center" }}>
            {onRegenerate && <button className="btn btn-ghost" style={{ marginRight: "auto", fontSize: 12.5 }} title="若你剛改了營業時間/菜單，按這個用最新資料重擬"
              onClick={async () => { const t = await onRegenerate(replyTarget); if (t) setDraft(t); }}><Icon name="refresh" size={13} /> 重新產生（最新資料）</button>}
            <button className="btn btn-ghost" onClick={() => onQueueReply(replyTarget, draft.trim())} disabled={!draft.trim()}>只存草稿</button>
            <button className="btn btn-primary" disabled={!draft.trim() || sending}
              onClick={async () => { setSending(true); const ok = await onSendReply(replyTarget, pendingReply, draft.trim()); setSending(false); if (ok) setOpen(false); }}>
              <Icon name="send" size={14} /> {sending ? "送出中…" : "送出給客人"}
            </button>
          </div>
        </div>
      )}

      {/* ── 聊天記錄 ── */}
      <div style={{ padding: "10px 14px 14px", background: "var(--surface-2)", borderTop: "1px solid var(--line)" }}>
        <div style={{ display: "flex", alignItems: "center", marginBottom: 8 }}>
          <span style={{ fontSize: 11.5, color: "var(--ink-3)", fontWeight: 700 }}>對話記錄</span>
          {timeline.length > 6 && <button className="btn btn-ghost" style={{ marginLeft: "auto", fontSize: 11.5 }} onClick={() => setShowAll(!showAll)}>{showAll ? "只看最近" : "看全部 " + timeline.length + " 則"}</button>}
        </div>
        <div style={{ display: "flex", flexDirection: "column", gap: 6 }}>
          {shown.map((it) => (
            <div key={it.key} style={{ display: "flex", justifyContent: it.side === "out" ? "flex-end" : "flex-start" }}>
              <div style={{ maxWidth: "78%", fontSize: 13, lineHeight: 1.5, whiteSpace: "pre-wrap", wordBreak: "break-word", padding: "7px 11px", borderRadius: 12, background: it.side === "out" ? "#06c755" : "var(--surface)", color: it.side === "out" ? "#fff" : "var(--ink)", border: it.side === "out" ? "none" : "1px solid var(--line)", cursor: it.side === "in" ? "pointer" : "default" }} onClick={it.side === "in" ? () => onCopy(it.text) : undefined} title={it.side === "in" ? "點擊複製" : ""}>{it.text}</div>
            </div>
          ))}
        </div>
      </div>
    </div>
  );
}

function LineTemplatesPanel({ templates, restaurants, onSave, onDelete }) {
  const { useState } = React;
  const [adding, setAdding] = useState(false);
  const [label, setLabel] = useState("");
  const [body, setBody] = useState("");
  const [rid, setRid] = useState(restaurants[0] ? restaurants[0].id : "");

  function save() {
    if (!label.trim() || !body.trim() || !rid) return;
    onSave({ restaurant_id: rid, label: label.trim(), body: body.trim() });
    setLabel(""); setBody(""); setAdding(false);
  }
  return (
    <div className="card" style={{ padding: 16, marginBottom: 16 }}>
      <div style={{ display: "flex", alignItems: "center", marginBottom: 10 }}>
        <strong style={{ fontSize: 15 }}><Icon name="msg" size={16} /> 預設回覆範本</strong>
        <button className="btn btn-ghost" style={{ marginLeft: "auto", fontSize: 13 }} onClick={() => setAdding(!adding)}>
          <Icon name="plus" size={14} /> 新增
        </button>
      </div>
      <div style={{ fontSize: 12.5, color: "var(--ink-3)", marginBottom: 10 }}>
        設好常用回覆，回訊息時一鍵套用。可用 <code>{"{name}"}</code> 代入客人名字。AI 之後會研究你送出的回覆來模仿你的口氣建議草稿。
      </div>
      {adding && (
        <div style={{ background: "var(--surface-2)", borderRadius: 10, padding: 12, marginBottom: 12 }}>
          {restaurants.length > 1 && (
            <select value={rid} onChange={(e) => setRid(e.target.value)} style={{ width: "100%", marginBottom: 8, padding: 8, borderRadius: 8, border: "1px solid var(--line)" }}>
              {restaurants.map((r) => <option key={r.id} value={r.id}>{r.name}</option>)}
            </select>
          )}
          <input value={label} onChange={(e) => setLabel(e.target.value)} placeholder="範本名稱（例：確認訂位）"
            style={{ width: "100%", marginBottom: 8, padding: 8, borderRadius: 8, border: "1px solid var(--line)", fontSize: 14 }} />
          <textarea value={body} onChange={(e) => setBody(e.target.value)} rows={3} placeholder="回覆內容…可用 {name} 代入客人名字"
            style={{ width: "100%", padding: 8, borderRadius: 8, border: "1px solid var(--line)", fontSize: 14, resize: "vertical", fontFamily: "inherit" }} />
          <div style={{ display: "flex", gap: 8, justifyContent: "flex-end", marginTop: 8 }}>
            <button className="btn btn-ghost" onClick={() => setAdding(false)}>取消</button>
            <button className="btn btn-primary" onClick={save}>儲存</button>
          </div>
        </div>
      )}
      {templates.length === 0 && !adding && <div style={{ fontSize: 13, color: "var(--ink-3)" }}>還沒有範本，點「新增」建立第一個。</div>}
      <div style={{ display: "flex", flexDirection: "column", gap: 8 }}>
        {templates.map((t) => (
          <div key={t.id} style={{ display: "flex", gap: 10, alignItems: "flex-start", padding: "8px 10px", border: "1px solid var(--line)", borderRadius: 8 }}>
            <div style={{ flex: 1, minWidth: 0 }}>
              <strong style={{ fontSize: 13.5 }}>{t.label}</strong>
              <div style={{ fontSize: 12.5, color: "var(--ink-2)", marginTop: 2, whiteSpace: "pre-wrap" }}>{t.body}</div>
            </div>
            <button className="btn btn-ghost btn-icon" style={{ color: "var(--ink-3)" }} onClick={() => onDelete(t.id)}><Icon name="trash" size={14} /></button>
          </div>
        ))}
      </div>
    </div>
  );
}

// ── LINE 設定教學（白話 + 各店 webhook 網址） ──
const LINE_WEBHOOK_BASE = "https://dcsbeeccaycrljjztgjs.supabase.co/functions/v1/line-inbound-webhook";

function LineSetupView() {
  const { useState } = React;
  const [toast, setToast] = useState("");
  const webhookUrl = LINE_WEBHOOK_BASE + "?restaurant_id=" + LINE_RESTAURANT_ID;
  function flash(t) { setToast(t); setTimeout(() => setToast(""), 1800); }
  async function copy(text) { try { await navigator.clipboard.writeText(text); flash("已複製"); } catch (e) { flash("複製失敗"); } }

  const steps = [
    { t: "確認官方帳號可接 Messaging API", d: "用電腦到 LINE Developers Console（developers.line.biz）用你的 LINE 登入。如果你目前只是在手機「LINE 官方帳號」App 手動回訊息，需要在這裡把同一個官方帳號的 Messaging API 啟用（同一個帳號、不是新開一個）。" },
    { t: "建立 / 打開 Messaging API channel", d: "在 Provider 底下建立或打開一個 Messaging API channel，對應到你這家餐廳的官方帳號。" },
    { t: "拿三把鑰匙", d: "在 channel 設定頁找到並複製：① Channel ID ② Channel secret ③ Channel access token（long-lived，按 Issue 產生）。這三個等一下要填進 ChefsMate。" },
    { t: "把鑰匙填進 ChefsMate 並開啟", d: "把上面三個值填進 ChefsMate 後台的 LINE 設定（restaurant_settings 的 line_channel_id / line_channel_secret / line_channel_access_token），並把「LINE 通知」開關（line_messaging_enabled）打開。填好後我這邊才驗得了章、抓得到客人名字。" },
    { t: "貼 Webhook 網址", d: "回到 channel 的 Messaging API 頁，找到「Webhook URL」，貼上「你這家店」的網址（下方可一鍵複製），按 Verify 應該回 Success，然後把「Use webhook」打開。" },
    { t: "關掉自動回應", d: "在「Auto-reply messages / 自動回應」把自動罐頭回覆關掉（不然客人會同時收到罐頭訊息）。Webhook 開、自動回應關。" },
    { t: "測試", d: "用另一支手機加你的官方帳號為好友，傳一則「想訂位 6/5 晚上 6 點 4 位」之類的訊息。回到「LINE 收件匣」分頁按重新整理，應該就看得到那則訊息了。" },
  ];

  return (
    <div>
      <PageHead icon="shield" color="#06c755" title="LINE 設定教學"
        desc="一次性把你的 LINE 官方帳號接上來。接好後，客人傳的訊息會自動進「LINE 收件匣」。" />

      <div className="card" style={{ padding: 16, marginBottom: 16, background: "#06c7550d", border: "1px solid #06c75533" }}>
        <strong style={{ fontSize: 14 }}>⓵ 海邊小巫的 Webhook 網址（步驟 5 要貼的）</strong>
        <div style={{ fontSize: 12.5, color: "var(--ink-3)", margin: "6px 0 12px" }}>貼到海邊小巫 LINE channel 的 Webhook URL 欄位。</div>
        <div style={{ display: "flex", gap: 8, alignItems: "center", padding: "8px 0", borderTop: "1px solid var(--line)" }}>
          <strong style={{ fontSize: 13, width: 88, flexShrink: 0 }}>{LINE_RESTAURANT_NAME}</strong>
          <code style={{ flex: 1, minWidth: 0, fontSize: 11.5, background: "var(--surface-2)", padding: "6px 8px", borderRadius: 6, overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }}>{webhookUrl}</code>
          <button className="btn btn-ghost" style={{ fontSize: 12.5, flexShrink: 0 }} onClick={() => copy(webhookUrl)}><Icon name="file" size={13} /> 複製</button>
        </div>
      </div>

      <div className="card" style={{ padding: 16 }}>
        <strong style={{ fontSize: 14 }}>⓶ 完整步驟（照順序做）</strong>
        <div style={{ marginTop: 12, display: "flex", flexDirection: "column", gap: 14 }}>
          {steps.map((s, i) => (
            <div key={i} style={{ display: "flex", gap: 12 }}>
              <div style={{ width: 26, height: 26, borderRadius: "50%", flexShrink: 0, background: "#06c755", color: "#fff", display: "grid", placeItems: "center", fontSize: 13, fontWeight: 700 }}>{i + 1}</div>
              <div style={{ flex: 1 }}>
                <strong style={{ fontSize: 13.5 }}>{s.t}</strong>
                <div style={{ fontSize: 13, color: "var(--ink-2)", lineHeight: 1.6, marginTop: 2 }}>{s.d}</div>
              </div>
            </div>
          ))}
        </div>
      </div>

      <div className="card" style={{ padding: 16, marginTop: 16 }}>
        <strong style={{ fontSize: 14 }}>常見問題</strong>
        <div style={{ fontSize: 13, color: "var(--ink-2)", lineHeight: 1.7, marginTop: 8 }}>
          <p><strong>Q：我可以一邊用手機 App 手動回、一邊用這個嗎？</strong><br/>可以。Webhook 只是「複製一份」客人訊息進來給我整理，不影響你在手機上看跟回。只是「自動罐頭回應」建議關掉，避免重複。</p>
          <p><strong>Q：為什麼不能讀我以前的舊訊息？</strong><br/>這是 LINE 的限制——官方帳號 API 沒有「抓歷史訊息」的功能，只能從「接好之後」往後收。所以越早接好，累積得越多。</p>
          <p><strong>Q：填鑰匙安全嗎？</strong><br/>三把鑰匙存在你自己的後台、只有伺服器讀得到（已上資安鎖）。我不會把 token 顯示在這個網頁上。</p>
        </div>
      </div>

      {toast && <div style={{ position: "fixed", bottom: 24, left: "50%", transform: "translateX(-50%)", background: "var(--ink-1)", color: "#fff", padding: "10px 18px", borderRadius: 10, fontSize: 13.5, zIndex: 50 }}>{toast}</div>}
    </div>
  );
}

// Phase 2：AI 大腦設定（自訂 webhook）+ 問答庫管理（可收合）
function LineAiPanel({ flash }) {
  const { useState, useEffect } = React;
  const store = window.consoleStore;
  const [openPanel, setOpenPanel] = useState(false);
  const [settings, setSettings] = useState({ provider: "none", url: "", secret: "", model: "", base_url: "", has_key: false, apiKey: "" });
  const [faqs, setFaqs] = useState([]);
  const [editing, setEditing] = useState(null); // {id?, category, keywords, question, answer, auto_reply_enabled}
  const [savingS, setSavingS] = useState(false);
  const [knowledge, setKnowledge] = useState([]);
  const [syncing, setSyncing] = useState(false);
  const [kbAdd, setKbAdd] = useState({ title: "", content: "" });
  const [bootstrapping, setBootstrapping] = useState(false);
  const [drafts, setDrafts] = useState(null); // [{question, answer, needs_recipe}]

  async function load() {
    const [s, f, k] = await Promise.all([
      store.lineAiSettings(LINE_RESTAURANT_ID),
      store.lineFaqList(LINE_RESTAURANT_ID),
      store.lineKnowledgeList(LINE_RESTAURANT_ID),
    ]);
    if (s) setSettings({ ...s, apiKey: "" });
    setFaqs(f || []);
    setKnowledge(k || []);
  }

  const KB_LABEL = { menu: "菜單", recipe: "食譜", info: "店家資訊", manual_reply: "客服紀錄", manual: "手動新增", faq: "問答庫", website: "官網", social: "社群" };
  async function resyncKnowledge() {
    setSyncing(true);
    const r = await store.lineKnowledgeSync(LINE_RESTAURANT_ID);
    setSyncing(false);
    if (r && r.ok) { await load(); flash("已重新同步菜單/食譜/店家資訊"); } else flash("同步失敗");
  }
  async function addKnowledge() {
    if (!kbAdd.content.trim()) { flash("請填內容"); return; }
    const r = await store.lineKnowledgeAddManual(LINE_RESTAURANT_ID, kbAdd.title.trim(), kbAdd.content.trim());
    if (r && r.ok) { setKbAdd({ title: "", content: "" }); await load(); flash("已加入記憶庫"); } else flash("加入失敗");
  }
  async function delKnowledge(id) {
    if (!window.confirm("從記憶庫刪除這筆？")) return;
    const ok = await store.lineKnowledgeDelete(id);
    if (ok) { await load(); flash("已刪除"); } else flash("刪除失敗");
  }
  async function runBootstrap() {
    setBootstrapping(true);
    const r = await store.lineFaqBootstrap(LINE_RESTAURANT_ID, 12);
    setBootstrapping(false);
    if (r && r.ok) {
      if (!r.drafts || r.drafts.length === 0) { flash(r.message || "公有問題庫還沒有問題"); return; }
      setDrafts(r.drafts.map((d) => ({ ...d })));
    } else flash((r && r.error === "ai_not_configured") ? "請先在上方設定 AI（選廠商+貼金鑰）" : "產生失敗");
  }
  async function adoptDraft(idx) {
    const d = drafts[idx];
    if (!d.answer || !d.answer.trim()) { flash("這題還沒有答案"); return; }
    const content = `客人常問：「${d.question}」建議回覆：「${d.answer.trim()}」`;
    const r = await store.lineKnowledgeAddManual(LINE_RESTAURANT_ID, d.question.slice(0, 40), content);
    if (r && r.ok) { setDrafts(drafts.filter((_, i) => i !== idx)); await load(); flash("已採用，加入記憶庫"); }
    else flash("採用失敗");
  }
  useEffect(() => { if (openPanel) load(); /* eslint-disable-next-line */ }, [openPanel]);

  async function saveSettings() {
    setSavingS(true);
    const r = await store.lineSetAiSettings(LINE_RESTAURANT_ID, settings);
    setSavingS(false);
    flash(r && r.ok ? "已儲存 AI 設定" : "儲存失敗");
  }
  function newFaq() { setEditing({ category: "", keywords: "", question: "", answer: "", auto_reply_enabled: false }); }
  function editFaq(f) { setEditing({ ...f, keywords: (f.keywords || []).join("、") }); }
  async function saveFaq() {
    if (!editing.answer || !editing.answer.trim()) { flash("請填答案"); return; }
    const saved = await store.lineFaqSave({ ...editing, restaurant_id: LINE_RESTAURANT_ID });
    if (saved) { setEditing(null); await load(); flash("已儲存問答"); } else flash("儲存失敗");
  }
  async function delFaq(id) {
    if (!window.confirm("刪除這題問答？")) return;
    const ok = await store.lineFaqDelete(id);
    if (ok) { await load(); flash("已刪除"); } else flash("刪除失敗");
  }

  const inputStyle = { width: "100%", borderRadius: 8, border: "1px solid var(--line)", padding: "8px 10px", fontSize: 13, fontFamily: "inherit", boxSizing: "border-box" };

  return (
    <div className="card" style={{ padding: 0, marginBottom: 12, overflow: "hidden" }}>
      <button onClick={() => setOpenPanel(!openPanel)} style={{ width: "100%", display: "flex", alignItems: "center", gap: 8, padding: 14, background: "none", border: "none", cursor: "pointer", textAlign: "left" }}>
        <Icon name="wand" size={15} />
        <strong style={{ fontSize: 14 }}>AI 大腦與問答庫</strong>
        <span className="chip" style={{ background: settings.provider !== "none" ? "#7c5cff18" : "var(--surface-2)", color: settings.provider !== "none" ? "#7c5cff" : "var(--ink-3)", fontSize: 11 }}>
          {settings.provider === "none" ? "未接 AI（只用問答庫）" : ("AI 已接：" + ({ openai: "OpenAI", anthropic: "Claude", gemini: "Gemini", webhook: "自訂 webhook" }[settings.provider] || settings.provider))}
        </span>
        <span style={{ marginLeft: "auto", fontSize: 12, color: "var(--ink-3)" }}>{openPanel ? "收合 ▲" : "展開 ▼"}</span>
      </button>

      {openPanel && (
        <div style={{ padding: 14, borderTop: "1px solid var(--line)", background: "var(--surface-2)" }}>
          {/* 分流說明 */}
          <div style={{ fontSize: 12, color: "var(--ink-3)", lineHeight: 1.6, marginBottom: 12 }}>
            訊息進來會：<b>① 訂位</b>自動判斷可訂位 → <b>② 命中下方問答庫關鍵字</b>用標準答案擬回覆 →
            <b>③ 你接的 AI</b>處理 → <b>④ 都不行</b>標「需真人回覆」。所有回覆一律先擬草稿，你過目才送出。
          </div>

          {/* AI provider 設定：每家店接自己的 AI */}
          <div style={{ marginBottom: 16 }}>
            <div style={{ fontSize: 12.5, fontWeight: 700, marginBottom: 6 }}>接你自己的 AI</div>
            <div style={{ display: "flex", gap: 8, alignItems: "center", marginBottom: 8, flexWrap: "wrap" }}>
              <span style={{ fontSize: 12.5, color: "var(--ink-3)" }}>用哪家：</span>
              <select value={settings.provider} onChange={(e) => setSettings({ ...settings, provider: e.target.value })}
                style={{ ...inputStyle, width: "auto", padding: "6px 10px" }}>
                <option value="none">不接（只用問答庫）</option>
                <option value="openai">OpenAI（貼自己金鑰）</option>
                <option value="anthropic">Claude（貼自己金鑰）</option>
                <option value="gemini">Gemini（貼自己金鑰）</option>
                <option value="webhook">自架 webhook（進階）</option>
              </select>
            </div>

            {/* 貼金鑰型 */}
            {(settings.provider === "openai" || settings.provider === "anthropic" || settings.provider === "gemini") && (
              <div style={{ display: "flex", flexDirection: "column", gap: 8 }}>
                <input style={inputStyle} placeholder={"模型名稱（例：" + ({ openai: "gpt-4o-mini", anthropic: "claude-haiku-4-5", gemini: "gemini-1.5-flash" }[settings.provider]) + "）"}
                  value={settings.model} onChange={(e) => setSettings({ ...settings, model: e.target.value })} />
                <input style={inputStyle} type="password" autoComplete="off"
                  placeholder={settings.has_key ? "API 金鑰（已設定，要換才需重填）" : "貼上你自己的 API 金鑰"}
                  value={settings.apiKey} onChange={(e) => setSettings({ ...settings, apiKey: e.target.value })} />
                <div style={{ fontSize: 11.5, color: "var(--ink-3)" }}>
                  用<b>你自己的</b>金鑰呼叫，費用算你自己的（關鍵字＋問答庫會先擋，真正打到 AI 的很少）。
                  金鑰只存後台、不會顯示原文；AI 只能「建議」，回覆仍由你過目才送出。
                  {settings.has_key && <span> 目前狀態：<b style={{ color: "#06c755" }}>已設定金鑰 ✓</b></span>}
                </div>
              </div>
            )}

            {/* 自架 webhook 型 */}
            {settings.provider === "webhook" && (
              <div style={{ display: "flex", flexDirection: "column", gap: 8 }}>
                <input style={inputStyle} placeholder="你的 AI webhook 網址（https://…）" value={settings.url} onChange={(e) => setSettings({ ...settings, url: e.target.value })} />
                <input style={inputStyle} placeholder="簽章密鑰 secret（選填）" value={settings.secret} onChange={(e) => setSettings({ ...settings, secret: e.target.value })} />
                <div style={{ fontSize: 11.5, color: "var(--ink-3)" }}>
                  我們把「訊息原文＋餐廳名＋問答庫＋訂位骨架」POST 到這個網址（帶 X-ChefsMate-Signature 簽章），
                  你的 AI 回 <code>{`{category, suggested_reply, confidence, needs_human}`}</code>。接法見技術文件 docs/LINE_AI_webhook接口.md。
                </div>
              </div>
            )}
            {/* 官網（抓進記憶庫）+ 升級開關 */}
            <div style={{ marginTop: 12, paddingTop: 12, borderTop: "1px dashed var(--line)", display: "flex", flexDirection: "column", gap: 8 }}>
              <input style={inputStyle} placeholder="餐廳官網網址（選填，https://…）— 會抓官網文字進記憶庫" value={settings.website_url || ""} onChange={(e) => setSettings({ ...settings, website_url: e.target.value })} />
              <div style={{ fontSize: 11.5, color: "var(--ink-3)" }}>填好官網後，按下方記憶庫的「重新同步」會把官網內容也抓進來。</div>
              <label style={{ fontSize: 12.5, color: "var(--ink-2)", display: "flex", alignItems: "flex-start", gap: 8 }}>
                <input type="checkbox" checked={settings.escalation === true} onChange={(e) => setSettings({ ...settings, escalation: e.target.checked })} style={{ marginTop: 3 }} />
                <span><b>AI 處理不了時，自動回「請真人回答」</b>：客人問了 AI 也答不出來的問題時，自動先回覆安撫客人（標 🤖），同時標記「需真人」讓你跟進。⚠️ 開了會自動送安撫訊息給客人。</span>
              </label>
            </div>
            <button className="btn btn-primary" style={{ marginTop: 8, fontSize: 13 }} disabled={savingS} onClick={saveSettings}>{savingS ? "儲存中…" : "儲存 AI 設定"}</button>
          </div>

          {/* 問答庫 */}
          <div style={{ display: "flex", alignItems: "center", marginBottom: 8 }}>
            <div style={{ fontSize: 12.5, fontWeight: 700 }}>問答庫（關鍵字 → 自動擬答）</div>
            <button className="btn btn-ghost" style={{ marginLeft: "auto", fontSize: 12.5 }} onClick={newFaq}>+ 新增問答</button>
          </div>
          {faqs.length === 0 && !editing && <div style={{ fontSize: 12, color: "var(--ink-3)", marginBottom: 8 }}>還沒有問答。常見如「營業時間 / 停車 / 低消 / 可帶寵物」。</div>}
          {faqs.map((f) => (
            <div key={f.id} style={{ display: "flex", gap: 8, alignItems: "flex-start", padding: "8px 0", borderBottom: "1px solid var(--line)" }}>
              <div style={{ flex: 1, minWidth: 0 }}>
                <div style={{ fontSize: 12.5, fontWeight: 600 }}>
                  {f.category || "（未分類）"} <span style={{ color: "var(--ink-3)", fontWeight: 400 }}>· 關鍵字：{(f.keywords || []).join("、") || "（無）"}</span>
                  {f.auto_reply_enabled && <span className="chip" style={{ background: "#06c75518", color: "#06c755", fontSize: 10, marginLeft: 6 }}>🤖 自動代答中</span>}
                  {!f.auto_reply_enabled && (f.answered_streak || 0) >= 3 && <span className="chip" style={{ background: "#e8a33d18", color: "#e8a33d", fontSize: 10, marginLeft: 6 }}>已答對 {f.answered_streak} 次，可開自動代答</span>}
                </div>
                <div style={{ fontSize: 12, color: "var(--ink-2)", marginTop: 2 }}>{f.answer}</div>
              </div>
              <button className="btn btn-ghost" style={{ fontSize: 12 }} onClick={() => editFaq(f)}>編輯</button>
              <button className="btn btn-ghost" style={{ fontSize: 12, color: "#ef4444" }} onClick={() => delFaq(f.id)}>刪</button>
            </div>
          ))}

          {editing && (
            <div style={{ marginTop: 12, padding: 12, borderRadius: 8, border: "1px solid var(--line)", background: "var(--surface)", display: "flex", flexDirection: "column", gap: 8 }}>
              <input style={inputStyle} placeholder="分類（例：營業時間）" value={editing.category} onChange={(e) => setEditing({ ...editing, category: e.target.value })} />
              <input style={inputStyle} placeholder="關鍵字（用、或逗號分隔，例：營業時間、幾點開）" value={editing.keywords} onChange={(e) => setEditing({ ...editing, keywords: e.target.value })} />
              <textarea style={{ ...inputStyle, resize: "vertical" }} rows={2} placeholder="標準答案（命中關鍵字時擬這段給客人）" value={editing.answer} onChange={(e) => setEditing({ ...editing, answer: e.target.value })} />
              <label style={{ fontSize: 12.5, color: "var(--ink-2)", display: "flex", alignItems: "flex-start", gap: 8 }}>
                <input type="checkbox" checked={editing.auto_reply_enabled === true} onChange={(e) => setEditing({ ...editing, auto_reply_enabled: e.target.checked })} style={{ marginTop: 3 }} />
                <span>🤖 <b>自動代答</b>：命中這題關鍵字時，直接自動回覆客人（標 🤖、附簽名），不用你過目。⚠️ 開了就會自動送，請確認答案正確再開。{(editing.answered_streak || 0) > 0 ? `（已答對 ${editing.answered_streak} 次）` : ""}</span>
              </label>
              <div style={{ display: "flex", gap: 8, justifyContent: "flex-end" }}>
                <button className="btn btn-ghost" style={{ fontSize: 13 }} onClick={() => setEditing(null)}>取消</button>
                <button className="btn btn-primary" style={{ fontSize: 13 }} onClick={saveFaq}>儲存問答</button>
              </div>
            </div>
          )}

          {/* ── AI 記憶庫（RAG）── */}
          <div style={{ marginTop: 20, borderTop: "1px dashed var(--line)", paddingTop: 14 }}>
            <div style={{ display: "flex", alignItems: "center", marginBottom: 6, flexWrap: "wrap", gap: 8 }}>
              <div style={{ fontSize: 12.5, fontWeight: 700 }}>AI 記憶庫（AI 回答前會查這裡，避免亂編）</div>
              <button className="btn btn-ghost" style={{ marginLeft: "auto", fontSize: 12.5 }} disabled={syncing} onClick={resyncKnowledge}>
                <Icon name="refresh" size={12} /> {syncing ? "同步中…" : "重新同步菜單/食譜/資訊"}
              </button>
            </div>
            <div style={{ fontSize: 11.5, color: "var(--ink-3)", marginBottom: 8 }}>
              自動收錄：菜單、食譜、營業時間/地址、你送出的客服回覆。改了菜單後按「重新同步」更新。
            </div>

            {/* 公有問題庫 onboarding */}
            <div style={{ marginBottom: 10, padding: 10, borderRadius: 8, background: "#7c5cff0d", border: "1px solid #7c5cff33" }}>
              <div style={{ display: "flex", alignItems: "center", gap: 8, flexWrap: "wrap" }}>
                <span style={{ fontSize: 12.5, fontWeight: 600 }}>🤖 一鍵預填常見問答</span>
                <span style={{ fontSize: 11.5, color: "var(--ink-3)" }}>抽別家客人最常問的題，用你的 AI＋菜單先擬好答案讓你審核</span>
                <button className="btn btn-primary" style={{ marginLeft: "auto", fontSize: 12.5 }} disabled={bootstrapping} onClick={runBootstrap}>{bootstrapping ? "產生中…" : "產生"}</button>
              </div>
              {drafts && drafts.length > 0 && (
                <div style={{ marginTop: 10, display: "flex", flexDirection: "column", gap: 8 }}>
                  {drafts.map((d, i) => (
                    <div key={i} style={{ padding: 8, borderRadius: 8, background: "var(--surface)", border: "1px solid var(--line)" }}>
                      <div style={{ fontSize: 12.5, fontWeight: 600, marginBottom: 4 }}>Q：{d.question}</div>
                      <textarea value={d.answer} onChange={(e) => setDrafts(drafts.map((x, j) => j === i ? { ...x, answer: e.target.value } : x))}
                        rows={2} style={{ width: "100%", borderRadius: 6, border: "1px solid var(--line)", padding: 8, fontSize: 12.5, resize: "vertical", boxSizing: "border-box", fontFamily: "inherit" }} />
                      {d.needs_recipe && (
                        <div style={{ fontSize: 11.5, color: "#e8a33d", marginTop: 4 }}>
                          💡 這題牽涉菜色成分，<b>到主 App（成本控制）建立食譜</b>後，AI 就能自動、更精準地回答（過敏原/葷素都查得到）。還沒用成本控制 App 的話可到 App Store 訂閱。
                        </div>
                      )}
                      <div style={{ display: "flex", gap: 8, justifyContent: "flex-end", marginTop: 6 }}>
                        <button className="btn btn-ghost" style={{ fontSize: 12 }} onClick={() => setDrafts(drafts.filter((_, j) => j !== i))}>略過</button>
                        <button className="btn btn-primary" style={{ fontSize: 12 }} onClick={() => adoptDraft(i)}>採用</button>
                      </div>
                    </div>
                  ))}
                </div>
              )}
            </div>
            {/* 來源統計 */}
            <div style={{ display: "flex", gap: 6, flexWrap: "wrap", marginBottom: 10 }}>
              {Object.entries(knowledge.reduce((a, k) => { a[k.source_type] = (a[k.source_type] || 0) + 1; return a; }, {})).map(([src, n]) => (
                <span key={src} className="chip" style={{ background: "var(--surface-2)", color: "var(--ink-2)", fontSize: 11 }}>{KB_LABEL[src] || src} {n}</span>
              ))}
              {knowledge.length === 0 && <span style={{ fontSize: 12, color: "var(--ink-3)" }}>記憶庫還是空的，按「重新同步」把菜單帶進來。</span>}
            </div>
            {/* 手動新增 */}
            <div style={{ display: "flex", gap: 8, marginBottom: 10, flexWrap: "wrap" }}>
              <input style={{ ...inputStyle, flex: "1 1 140px" }} placeholder="標題（例：停車）" value={kbAdd.title} onChange={(e) => setKbAdd({ ...kbAdd, title: e.target.value })} />
              <input style={{ ...inputStyle, flex: "2 1 240px" }} placeholder="內容（例：店門口有 5 個免費停車位）" value={kbAdd.content} onChange={(e) => setKbAdd({ ...kbAdd, content: e.target.value })} />
              <button className="btn btn-primary" style={{ fontSize: 13 }} onClick={addKnowledge}>加入</button>
            </div>
            {/* 列表（最多顯示 30） */}
            <div style={{ display: "flex", flexDirection: "column", gap: 4, maxHeight: 220, overflowY: "auto" }}>
              {knowledge.slice(0, 60).map((k) => (
                <div key={k.id} style={{ display: "flex", gap: 8, alignItems: "flex-start", fontSize: 12, padding: "4px 0", borderBottom: "1px solid var(--line)" }}>
                  <span className="chip" style={{ background: "var(--surface-2)", color: "var(--ink-3)", fontSize: 10, flexShrink: 0 }}>{KB_LABEL[k.source_type] || k.source_type}</span>
                  <span style={{ flex: 1, minWidth: 0, color: "var(--ink-2)" }}>{k.content}</span>
                  {(k.source_type === "manual" || k.source_type === "manual_reply") && <button className="btn btn-ghost" style={{ fontSize: 11, color: "#ef4444", padding: "0 6px" }} onClick={() => delKnowledge(k.id)}>刪</button>}
                </div>
              ))}
            </div>
          </div>
        </div>
      )}
    </div>
  );
}

// 把訊息依 LINE 帳號分組成「對話」，每組依最新訊息時間由新到舊排序
function groupConversations(msgs) {
  const byUser = {};
  (msgs || []).forEach((m) => {
    const k = m.line_user_id || ("__noid__" + m.id);
    (byUser[k] = byUser[k] || []).push(m);
  });
  return Object.keys(byUser).map((k) => {
    const group = byUser[k];
    const latestT = group.reduce((mx, m) => Math.max(mx, new Date(m.received_at).getTime() || 0), 0);
    return { key: k, msgs: group, latestT };
  }).sort((a, b) => b.latestT - a.latestT);
}

function LineInboxView() {
  const { useState, useEffect } = React;
  const store = window.consoleStore;
  const [authed, setAuthed] = useState(store && store.isAuthed && store.isAuthed());
  const [status, setStatus] = useState("all");
  const [msgs, setMsgs] = useState([]);
  const [templates, setTemplates] = useState([]);
  const [drafts, setDrafts] = useState({});
  const [replies, setReplies] = useState({});
  const [calSetting, setCalSetting] = useState(null);
  const [loading, setLoading] = useState(true);
  const [toast, setToast] = useState("");
  // 固定海邊小巫一家店
  const restaurants = [{ id: LINE_RESTAURANT_ID, name: LINE_RESTAURANT_NAME }];

  async function refresh() {
    if (!store || !store.isAuthed || !store.isAuthed()) { setAuthed(false); setLoading(false); return; }
    setAuthed(true); setLoading(true);
    const [ms, ts, dr, rp, cal] = await Promise.all([
      store.lineList({ restaurantId: LINE_RESTAURANT_ID, status }),
      store.lineTemplates(LINE_RESTAURANT_ID),
      store.lineDrafts(LINE_RESTAURANT_ID),
      store.lineReplies(LINE_RESTAURANT_ID),
      store.lineCalendarSetting(LINE_RESTAURANT_ID),
    ]);
    setMsgs(ms); setTemplates(ts); setDrafts(dr || {}); setReplies(rp || {}); setCalSetting(cal); setLoading(false);
  }

  async function toggleCalendarAutoAdd(next) {
    setCalSetting((c) => ({ ...(c || {}), enabled: next })); // 樂觀更新
    const r = await store.lineSetCalendarSetting(LINE_RESTAURANT_ID, next);
    if (r && r.ok) flash(next ? "已開啟：訂位確認後自動加入行事曆" : "已關閉自動加入行事曆");
    else { setCalSetting((c) => ({ ...(c || {}), enabled: !next })); flash("更新失敗，請重試"); } // rollback
  }
  useEffect(() => { refresh(); /* eslint-disable-next-line */ }, [status]);
  // 即時更新:有新訊息/回覆/狀態變更時自動刷新（Supabase Realtime）
  useEffect(() => {
    if (!store || !store.lineSubscribe || !store.isAuthed || !store.isAuthed()) return;
    const unsub = store.lineSubscribe(LINE_RESTAURANT_ID, () => refresh());
    return unsub;
    /* eslint-disable-next-line */
  }, []);

  function flash(t) { setToast(t); setTimeout(() => setToast(""), 1800); }
  async function copyMsg(text) {
    try { await navigator.clipboard.writeText(text || ""); flash("已複製訊息內容"); }
    catch (e) { flash("複製失敗，請手動選取"); }
  }
  async function setStatusFor(id, s) {
    const ok = await store.lineSetStatus(id, s);
    if (ok) { setMsgs((m) => m.map((x) => x.id === id ? { ...x, status: s } : x)); flash("已更新狀態"); }
  }
  async function queueReply(msg, text) {
    if (!text) return;
    const r = await store.lineQueueReply({
      restaurant_id: msg.restaurant_id, inbound_message_id: msg.id,
      line_user_id: msg.line_user_id, reply_text: text, source: "manual",
    });
    if (r) { setDrafts((d) => ({ ...d, [msg.id]: r })); flash("已存成待送出草稿"); }
    else flash("存草稿失敗");
  }
  // 送出回覆給客人:有草稿就送該草稿;沒有先建一筆再送
  async function sendReply(msg, pendingReply, text) {
    if (!text) return false;
    let replyId = pendingReply && pendingReply.id;
    if (!replyId) {
      const r = await store.lineQueueReply({
        restaurant_id: msg.restaurant_id, inbound_message_id: msg.id,
        line_user_id: msg.line_user_id, reply_text: text, source: "manual",
      });
      if (!r) { flash("送出失敗（草稿建立失敗）"); return false; }
      replyId = r.id;
    }
    let res = await store.lineSendReply(replyId, text);
    // ⭐ 防假承諾:位子可能已沒 → 提醒先確認接單
    if (res && res.needs_recheck) {
      const suggest = (res.suggest || []).join("、");
      const go = window.confirm(
        "⚠️ " + (res.message || "位子可能已經沒了") +
        (suggest ? ("\n\n當天還可訂：" + suggest) : "") +
        "\n\n按「確定」＝仍要送出這則回覆（不建議，除非你已改成改期/客滿內容）。\n按「取消」＝先去按『確認接單』核實。"
      );
      if (!go) return false;
      res = await store.lineSendReply(replyId, text, true);
    }
    if (res && res.ok) {
      setMsgs((m) => m.map((x) => x.id === msg.id ? { ...x, status: "replied" } : x));
      setDrafts((d) => { const n = { ...d }; delete n[msg.id]; return n; });
      flash("已送出給客人 ✓");
      return true;
    }
    flash("送出失敗：" + ((res && res.error) || "未知錯誤"));
    return false;
  }
  // 用最新資料重新產生草稿（改了營業時間/菜單後草稿過時時用）
  async function regenerateDraft(msg) {
    flash("重新產生中…（用最新資料）");
    const r = await store.lineRegenerateDraft(msg.id);
    if (r && r.ok) { await refresh(); flash("已用最新資料重新產生草稿 ✓"); return r.reply_text; }
    flash("重新產生失敗：" + ((r && r.error) || "未知錯誤"));
    return null;
  }
  // 確認接單 → 寫進 ChefsMate（含超賣處理 + 一鍵改期）
  async function confirmReservation(msg, payload) {
    let res = await store.lineConfirmReservation({ message_id: msg.id, ...payload });
    // 超賣警示：紅字提示，讓老闆決定「仍要接單(force)」或「通知客人改期」
    if (res && res.overbook && !payload.force) {
      const suggest = (res.suggest || []).join("、");
      const go = window.confirm(
        "⚠️ 超賣警告：" + (res.message || "此時段已被其他管道訂滿") +
        "。\n\n按「確定」＝仍要接單（會超賣，需自行加桌/併桌）。\n按「取消」＝幫您擬一則『改期通知』草稿給客人" +
        (suggest ? ("（當天可訂：" + suggest + "）") : "") + "。"
      );
      if (go) {
        res = await store.lineConfirmReservation({ message_id: msg.id, ...payload, force: true });
      } else {
        const nm = (msg.parse_result && msg.parse_result.name) || "";
        const text = nm + " 您好，不好意思這個時段剛好被訂滿了🙏 " + (suggest ? ("當天還可以訂：" + suggest + "，") : "") + "幫您換一個時間好嗎？";
        await store.lineQueueReply({ restaurant_id: msg.restaurant_id, inbound_message_id: msg.id, line_user_id: msg.line_user_id, reply_text: text, source: "manual" });
        flash("已擬好『改期通知』草稿，請到下方回覆送出");
        refresh();
        return false;
      }
    }
    if (res && res.ok) {
      setMsgs((m) => m.map((x) => x.id === msg.id ? { ...x, chefsmate_reservation_id: res.reservation_id, status: "calendared" } : x));
      flash(res.overbook ? "已接單（注意：超賣，請安排加桌/併桌）" : "已寫進 ChefsMate 正式訂位 ✓");
      return true;
    }
    flash("建立訂位失敗：" + ((res && (res.error || (res.detail && res.detail.error))) || "未知錯誤"));
    return false;
  }
  async function saveTemplate(t) { const r = await store.lineSaveTemplate(t); if (r) { flash("範本已儲存"); refresh(); } }
  async function delTemplate(id) { const ok = await store.lineDeleteTemplate(id); if (ok) { flash("已刪除範本"); refresh(); } }

  if (!authed) {
    return <EmptyState text="請先用右上角登入（Google）以讀取你的 LINE 收件匣。" />;
  }

  const statusChips = [["all", "全部"], ["new", "新訊息"], ["needs_reply", "待回覆"], ["replied", "已回覆"], ["archived", "已封存"]];

  return (
    <div>
      <PageHead icon="msg" color="#06c755" title="LINE 收件匣"
        desc="客人傳到官方帳號的訊息會即時出現在這裡。複製內容、設預設回覆、擬回覆草稿（你確認後才送）。" />

      {/* 狀態篩選（固定海邊小巫一家店） */}
      <div style={{ display: "flex", gap: 8, flexWrap: "wrap", marginBottom: 14, alignItems: "center" }}>
        <span className="chip" style={{ background: "#06c75518", color: "#06c755" }}><Icon name="user" size={11} /> {LINE_RESTAURANT_NAME}</span>
        <span className="chip" style={{ background: "#10b98118", color: "#10b981", fontSize: 11 }} title="新訊息與狀態變更會即時自動更新">● 即時</span>
        {statusChips.map(([k, label]) => (
          <button key={k} className={"chip" + (status === k ? " active" : "")} style={{ cursor: "pointer", background: status === k ? "var(--accent)" : "var(--surface-2)", color: status === k ? "#fff" : "var(--ink-2)", border: "1px solid var(--line)" }} onClick={() => setStatus(k)}>{label}</button>
        ))}
        <button className="btn btn-ghost" style={{ marginLeft: "auto", fontSize: 13 }} onClick={refresh}><Icon name="refresh" size={14} /> 重新整理</button>
      </div>

      {/* 行事曆自動加入開關（鏡射 POS 設定 auto_add_to_calendar） */}
      {calSetting && (
        <div className="card" style={{ padding: 14, marginBottom: 12, display: "flex", alignItems: "center", gap: 12, flexWrap: "wrap" }}>
          <div style={{ flex: 1, minWidth: 220 }}>
            <div style={{ display: "flex", alignItems: "center", gap: 8 }}>
              <Icon name="calendar" size={15} />
              <strong style={{ fontSize: 14 }}>訂位確認後自動加入行事曆</strong>
            </div>
            <div style={{ fontSize: 12, color: "var(--ink-3)", marginTop: 4, lineHeight: 1.5 }}>
              開啟後，LINE 接單（或網站、手動）的訂位排進 POS 時，POS 會自動把它加入你的裝置行事曆，只會有一筆、不重複。
              {calSetting.hasCalendarPicked
                ? " 已在 POS 選好要加入的行事曆。"
                : " ⚠️ 還沒在 POS 選過要加到哪一本行事曆 —— 請到 POS『設定 → 行事曆』選一次（不選會用裝置預設行事曆）。"}
            </div>
          </div>
          <button
            onClick={() => toggleCalendarAutoAdd(!calSetting.enabled)}
            aria-label="切換自動加入行事曆"
            style={{ position: "relative", width: 48, height: 28, borderRadius: 999, border: "none", cursor: "pointer", flexShrink: 0, background: calSetting.enabled ? "#06c755" : "var(--line)", transition: "background .15s" }}
          >
            <span style={{ position: "absolute", top: 3, left: calSetting.enabled ? 23 : 3, width: 22, height: 22, borderRadius: "50%", background: "#fff", transition: "left .15s", boxShadow: "0 1px 3px rgba(0,0,0,0.3)" }} />
          </button>
        </div>
      )}

      <LineAiPanel flash={flash} />

      <LineTemplatesPanel templates={templates} restaurants={restaurants} onSave={saveTemplate} onDelete={delTemplate} />

      {loading ? <EmptyState text="載入中…" />
        : msgs.length === 0
          ? <EmptyState text="目前沒有訊息。客人傳訊息到海邊小巫的 LINE@ 後就會出現在這裡（需先完成 LINE 設定，見「LINE 設定教學」分頁）。" />
          : groupConversations(msgs).map((group) => (
            <ConversationCard key={group.key} msgs={group.msgs} templates={templates}
              draftsByMsg={drafts} repliesByUser={replies}
              onStatus={setStatusFor} onQueueReply={queueReply} onCopy={copyMsg}
              onSendReply={sendReply} onConfirm={confirmReservation} onRegenerate={regenerateDraft} />
          ))}

      {toast && (
        <div style={{ position: "fixed", bottom: 24, left: "50%", transform: "translateX(-50%)", background: "var(--ink-1)", color: "#fff", padding: "10px 18px", borderRadius: 10, fontSize: 13.5, zIndex: 50, boxShadow: "0 4px 20px rgba(0,0,0,0.2)" }}>{toast}</div>
      )}
    </div>
  );
}
