// =============================================================================
// Loop — src/api.jsx   (unified data layer with automatic mock fallback)
//
// Exposes window.LoopData. If window.LOOP_CONFIG (url + anonKey) is present AND
// supabase-js loaded, it runs in LIVE mode against Supabase. Otherwise it runs
// in MOCK mode using window.Mock (from data.jsx) so the UI still works offline.
//
// Load order in index.html:
//   data.jsx  →  api.jsx  →  ...components...  →  app.jsx
// =============================================================================
(function () {
  const cfg = window.LOOP_CONFIG || {};
  const hasLive = !!(cfg.url && cfg.anonKey && window.supabase && window.supabase.createClient);
  const useLocal = Object.prototype.hasOwnProperty.call(cfg, "localApi"); // opt into local Node backend

  // Widget visitor id lives only for this page load: every refresh/new visit
  // starts a clean conversation (no old chat history), while closing and
  // reopening the bubble on the same page keeps the ongoing chat.
  const pageVisitorId = "v_" + Math.random().toString(36).slice(2, 11);
  try { localStorage.removeItem("loop_vid"); } catch (e) {} // drop ids stored by older builds

  // ---------------------------------------------------------------------------
  // shared mapping helpers
  // ---------------------------------------------------------------------------
  const initials = (name) =>
    (name || "?").split(" ").map((p) => p[0]).slice(0, 2).join("").toUpperCase();
  const minsAgo = (ts) =>
    Math.max(0, Math.round((Date.now() - new Date(ts).getTime()) / 60000));

  // ===========================================================================
  // LIVE implementation (Supabase)
  // ===========================================================================
  const live = (function () {
    if (!hasLive) return null;
    const sb = window.supabase.createClient(cfg.url, cfg.anonKey, {
      realtime: { params: { eventsPerSecond: 5 } },
    });
    const byId = { property: {}, category: {} };

    function mapConversation(row) {
      const prop = byId.property[row.property_id] || {};
      const cust = row.customer || {};
      const cat = byId.category[row.category_id];
      const msgs = (row.messages || [])
        .sort((a, b) => new Date(a.created_at) - new Date(b.created_at))
        .map((m) => ({ who: m.direction === "inbound" ? "v" : "a", t: m.body, at: m.created_at }));
      return {
        id: row.id,
        visitor: cust.display_name || "Visitor",
        username: cust.web_visitor_id || cust.email || "",
        initials: initials(cust.display_name),
        color: prop.color || "#A6F84A",
        site: prop.slug,
        channel: row.channel,
        concern: cat ? cat.key : "others",
        status: row.status,
        page: row.page_url || "",
        last: msgs.length ? msgs[msgs.length - 1].t : "",
        unread: row.unread_count || 0,
        minutes: minsAgo(row.last_message_at),
        tags: row.tags || [],
        messages: msgs,
        _raw: row,
      };
    }

    return {
      sb,
      _agentId: null,
      setCurrentAgent(id) { this._agentId = id; },

      async bootstrap() {
        const [props, agents, cats, canned] = await Promise.all([
          sb.from("properties").select("*").order("name"),
          sb.from("agents").select("*, agent_properties(properties(slug))").order("name"),
          sb.from("categories").select("*"),
          sb.from("canned_responses").select("*"),
        ]);
        if (props.error) throw props.error;

        (props.data || []).forEach((p) => (byId.property[p.id] = p));
        (cats.data || []).forEach((c) => (byId.category[c.id] = c));

        const websites = (props.data || []).map((p) => ({
          id: p.slug, _uuid: p.id, name: p.name, domain: p.domain,
          initials: initials(p.name), color: p.color, team: p.team,
          allowed: p.allowed_origins || [], online: true, activeChats: 0, waiting: 0,
        }));
        const agentList = (agents.data || []).map((a) => ({
          id: a.key, _uuid: a.id, name: a.name, email: a.email,
          role: a.role === "admin" ? "Admin" : "CSR", avatar: initials(a.name),
          color: a.avatar_color, status: a.status,
          websites: (a.agent_properties || []).map((ap) => ap.properties && ap.properties.slug).filter(Boolean),
          csat: a.csat, avg: a.avg_response,
        }));
        const concerns = (cats.data || []).map((c) => ({
          id: c.key, label: c.label, icon: c.icon, tint: c.tint, _uuid: c.id,
        }));
        const cannedResponses = (canned.data || []).map((r) => ({
          id: r.key, title: r.title, shortcut: r.shortcut, body: r.body,
        }));

        // initial conversation snapshot (for dashboard preview + window.Mock readers)
        const snap = await this.loadConversations({});
        const overviewStats = computeStats(snap, agentList, websites);

        window.Mock = Object.assign({}, window.Mock, {
          websites, agents: agentList, concerns, cannedResponses,
          conversations: snap, overviewStats,
        });
      },

      async loadConversations() {
        const { data, error } = await sb
          .from("conversations")
          .select("*, customer:customers(*), messages(direction,sender_type,body,created_at)")
          .order("last_message_at", { ascending: false })
          .limit(200);
        if (error) throw error;
        return (data || []).map(mapConversation);
      },

      async sendAgentMessage(conversationId, body, image) {
        const text = (body || "").trim();
        if (!text && !image) return null;
        const { data, error } = await sb.from("messages").insert({
          conversation_id: conversationId, direction: "outbound",
          sender_type: "agent", sender_id: this._agentId, body: text,
          attachments: image ? [{ type: "image", url: image }] : [],
        }).select("id, created_at").single();
        if (error) throw error;
        await sb.from("conversations")
          .update({ status: "active", unread_count: 0 }).eq("id", conversationId);
        const ch = sb.channel("conv:" + conversationId);
        await ch.subscribe();
        await ch.send({ type: "broadcast", event: "message",
          payload: { who: "a", t: text, image, at: data.created_at } });
        return data;
      },

      onChange(cb) {
        const ch = sb.channel("agent-rt")
          .on("postgres_changes", { event: "INSERT", schema: "public", table: "messages" }, cb)
          .on("postgres_changes", { event: "*", schema: "public", table: "conversations" }, cb)
          .subscribe();
        return () => sb.removeChannel(ch);
      },

      // widget
      async widgetStart(opts) {
        const res = await fetch(cfg.functionsUrl + "/widget-session", {
          method: "POST",
          headers: { "Content-Type": "application/json", apikey: cfg.anonKey },
          body: JSON.stringify({
            propertySlug: opts.propertySlug, name: opts.name,
            visitorId: pageVisitorId, pageUrl: opts.pageUrl,
          }),
        }).then((r) => r.json());
        if (!res || res.error) return { simulate: true, error: res && res.error };
        return res; // { conversationId, token, messages, property }
      },
      async widgetSend(session, text) {
        return fetch(cfg.functionsUrl + "/widget-messages", {
          method: "POST",
          headers: { "Content-Type": "application/json", apikey: cfg.anonKey },
          body: JSON.stringify({ token: session.token, body: text }),
        }).then((r) => r.json());
      },
      onWidgetReply(conversationId, cb) {
        const ch = sb.channel("conv:" + conversationId)
          .on("broadcast", { event: "message" }, (m) => cb(m.payload))
          .subscribe();
        return () => sb.removeChannel(ch);
      },
      async closeConversation(id) { await sb.from("conversations").update({ status: "closed" }).eq("id", id); return { ok: true }; },
      async setTags(id, tags) { await sb.from("conversations").update({ tags }).eq("id", id); return { ok: true }; },
      async assignConversation(id, agentId) { await sb.from("conversations").update({ agent_id: agentId }).eq("id", id); return { ok: true }; },
      async widgetRate() { return { ok: true }; },
      async getSettings() { window.Mock._settings = window.Mock._settings || { autoReply: true, brand: "Loop", hours: "We're online 24/7.", rules: [] }; return window.Mock._settings; },
      async saveSettings(patch) { window.Mock._settings = Object.assign((window.Mock._settings || {}), patch); return window.Mock._settings; },
      async getAnalytics() { const C = window.Mock.conversations || []; const by = (k) => C.reduce((m, c) => { const x = c[k] || "unknown"; m[x] = (m[x] || 0) + 1; return m; }, {}); const rated = C.filter(c => c.rating && c.rating.stars); return { total: C.length, byChannel: by("channel"), byStatus: by("status"), byConcern: by("concern"), ratedCount: rated.length, avgRating: rated.length ? Math.round(rated.reduce((s, c) => s + c.rating.stars, 0) / rated.length * 10) / 10 : 0, agents: { online: (window.Mock.agents || []).filter(a => a.status === "online").length, total: (window.Mock.agents || []).length }, websites: (window.Mock.websites || []).length }; },
      _arr(kind) { return kind === "agents" ? window.Mock.agents : kind === "websites" ? window.Mock.websites : kind === "concerns" ? window.Mock.concerns : window.Mock.cannedResponses; },
      async adminCreate(kind, obj) { const arr = this._arr(kind); const id = (obj.slug || obj.name || obj.title || obj.label || kind).toLowerCase().replace(/[^a-z0-9]+/g, "-").slice(0, 20) + "-" + Math.random().toString(36).slice(2, 6); const item = Object.assign({ id }, obj); arr.push(item); return item; },
      async adminUpdate(kind, id, patch) { const it = this._arr(kind).find(x => x.id === id); if (it) Object.assign(it, patch); return it || {}; },
      async adminDelete(kind, id) { const key = kind === "canned" ? "cannedResponses" : kind; window.Mock[key] = (window.Mock[key] || []).filter(x => x.id !== id); return { ok: true }; },
    };
  })();

  function computeStats(convs, agents, websites) {
    const base = (window.Mock && window.Mock.overviewStats) || [];
    const active = convs.filter((c) => c.status === "active").length;
    const waiting = convs.filter((c) => c.status === "waiting").length;
    const online = agents.filter((a) => a.status === "online").length;
    const patch = {
      active: active, waiting: waiting,
      online: online + " / " + agents.length, websites: websites.length,
    };
    return base.map((s) => (patch[s.id] != null ? Object.assign({}, s, { value: patch[s.id] }) : s));
  }

  // ===========================================================================
  // MOCK implementation (window.Mock from data.jsx)
  // ===========================================================================
  const mock = {
    _agentId: null,
    setCurrentAgent() {},
    async bootstrap() { /* window.Mock already populated by data.jsx */ },
    async loadConversations() { return (window.Mock.conversations || []).slice(); },
    async sendAgentMessage(conversationId, body, image) {
      const text = (body || "").trim();
      const c = (window.Mock.conversations || []).find((x) => x.id === conversationId);
      if (c) { c.messages = [...c.messages, { who: "a", t: text, image }]; c.last = text || (image ? "📷 Image" : ""); c.status = "active"; }
      return { simulated: true };
    },
    onChange() { return function () {}; },
    onInbound() { return function () {}; },
    onCall() { return function () {}; },
    sendCall() {},
    async widgetStart() { return { simulate: true }; },
    async widgetSend() { return { ok: true, simulate: true }; },
    onWidgetReply() { return function () {}; },
    async closeConversation(id) { const c = (window.Mock.conversations || []).find((x) => x.id === id); if (c) { c.status = "closed"; c.messages = [...c.messages, { who: "s", t: "Support has closed the chat." }]; } return { ok: true }; },
    async setConversationAi(id, enabled) { const c = (window.Mock.conversations || []).find((x) => x.id === id); if (c) c._aiMode = !!enabled; return { ok: true, aiMode: !!enabled }; },
    async setTags(id, tags) { const c = (window.Mock.conversations || []).find((x) => x.id === id); if (c) c.tags = tags; return { ok: true }; },
    async assignConversation(id, agentId) { const c = (window.Mock.conversations || []).find((x) => x.id === id); if (c) c.assignee = agentId; return { ok: true }; },
    async widgetRate() { return { ok: true }; },
    async widgetRequestHuman() { return { ok: true }; },
    async getSettings() { window.Mock._settings = window.Mock._settings || { autoReply: true, brand: "Loop", hours: "We're online 24/7.", rules: [] }; return window.Mock._settings; },
    async saveSettings(patch) { window.Mock._settings = Object.assign((window.Mock._settings || {}), patch); return window.Mock._settings; },
    async getAnalytics() { const C = window.Mock.conversations || []; const by = (k) => C.reduce((m, c) => { const x = c[k] || "unknown"; m[x] = (m[x] || 0) + 1; return m; }, {}); const rated = C.filter(c => c.rating && c.rating.stars); return { total: C.length, byChannel: by("channel"), byStatus: by("status"), byConcern: by("concern"), ratedCount: rated.length, avgRating: rated.length ? Math.round(rated.reduce((s, c) => s + c.rating.stars, 0) / rated.length * 10) / 10 : 0, agents: { online: (window.Mock.agents || []).filter(a => a.status === "online").length, total: (window.Mock.agents || []).length }, websites: (window.Mock.websites || []).length }; },
    _arr(kind) { return kind === "agents" ? window.Mock.agents : kind === "websites" ? window.Mock.websites : kind === "concerns" ? window.Mock.concerns : window.Mock.cannedResponses; },
    async adminCreate(kind, obj) { const arr = this._arr(kind); const id = (obj.slug || obj.name || obj.title || obj.label || kind).toLowerCase().replace(/[^a-z0-9]+/g, "-").slice(0, 20) + "-" + Math.random().toString(36).slice(2, 6); const item = Object.assign({ id }, obj); arr.push(item); return item; },
    async adminUpdate(kind, id, patch) { const it = this._arr(kind).find(x => x.id === id); if (it) Object.assign(it, patch); return it || {}; },
    async adminDelete(kind, id) { const key = kind === "canned" ? "cannedResponses" : kind; window.Mock[key] = (window.Mock[key] || []).filter(x => x.id !== id); return { ok: true }; },
  };

  // ===========================================================================
  // Facade
  // ===========================================================================
  // ===========================================================================
  // LOCAL implementation (self-hosted Node backend over fetch + WebSocket)
  // ===========================================================================
  const local = (function () {
    if (!useLocal) return null;
    const base = cfg.localApi || ""; // "" = same origin (served by the backend)
    let token = (typeof localStorage !== "undefined" && localStorage.getItem("loop_token")) || null;
    const H = () => { const h = { "Content-Type": "application/json" }; if (token) h.Authorization = "Bearer " + token; return h; };
    const wsUrl = (function () {
      const proto = location.protocol === "https:" ? "wss" : "ws";
      const host = base ? base.replace(/^https?:\/\//, "") : location.host;
      return proto + "://" + host + "/realtime";
    })();
    let ws = null; const changeCbs = new Set(); const widgetCbs = {}; const inboundCbs = new Set(); const callCbs = new Set();
    function wsSend(obj) { try { if (ws && ws.readyState === 1) ws.send(JSON.stringify(obj)); } catch (e) {} }
    // (Re)identify this socket: agent session + any watched conversations.
    function handshake() {
      if (token) wsSend({ type: "auth", token });
      Object.keys(widgetCbs).forEach((id) => wsSend({ type: "watch", conversationId: id }));
    }
    function ensureWS() {
      if (ws && ws.readyState <= 1) return;
      ws = new WebSocket(wsUrl);
      ws.onopen = handshake;
      ws.onmessage = (ev) => {
        let m; try { m = JSON.parse(ev.data); } catch (e) { return; }
        if (m.type === "change") changeCbs.forEach((cb) => cb(m));
        if (m.type === "inbound") inboundCbs.forEach((cb) => cb(m));
        if (m.type === "call") callCbs.forEach((cb) => cb(m));
        if (m.type === "widget" && widgetCbs[m.conversationId]) widgetCbs[m.conversationId](m.payload);
      };
      ws.onclose = () => { setTimeout(ensureWS, 1500); };
    }
    const J = (r) => r.json();
    // A signed-in console must NEVER render the bundled demo dataset — it
    // reads as another client's chats/brands. While real data loads (or after
    // a failed fetch) show an empty workspace instead, and keep retrying.
    let bootRetryTimer = null;
    function blankWorkspace() {
      const stats = ((window.Mock && window.Mock.overviewStats) || []).map((s) =>
        Object.assign({}, s, { value: s.id === "online" ? "0 / 0" : s.id === "avg" ? "—" : 0, delta: "", trend: "flat" }));
      window.Mock = Object.assign({}, window.Mock, { websites: [], agents: [], conversations: [], overviewStats: stats });
    }
    const api = {
      setCurrentAgent() {},
      async bootstrap() {
        // Anonymous callers (landing page, widget preview) get 401 here — they
        // keep the seeded mock data so marketing components still render.
        const authed = !!token;
        if (bootRetryTimer) { clearTimeout(bootRetryTimer); bootRetryTimer = null; }
        try {
          const r = await fetch(base + "/api/bootstrap", { headers: H() });
          if (authed && r.status === 401) {
            // Stale session (e.g. the server restarted) — drop it and return
            // to the login screen rather than showing any stand-in data.
            token = null;
            try { localStorage.removeItem("loop_token"); } catch (e) {}
            blankWorkspace();
            try { location.reload(); } catch (e) {}
            return;
          }
          const b = await r.json();
          if (b && b.websites) {
            window.Mock = Object.assign({}, window.Mock, {
              websites: b.websites, agents: b.agents, concerns: b.concerns,
              cannedResponses: b.cannedResponses, conversations: b.conversations || [],
              overviewStats: b.overviewStats || (window.Mock && window.Mock.overviewStats) || [],
            });
          } else if (authed) {
            blankWorkspace();
            bootRetryTimer = setTimeout(() => { api.bootstrap().catch(() => {}); }, 3000);
          }
        } catch (e) {
          if (authed) {
            blankWorkspace();
            bootRetryTimer = setTimeout(() => { api.bootstrap().catch(() => {}); }, 3000);
          }
        }
        ensureWS();
      },
      async loadConversations() { return fetch(base + "/api/conversations", { headers: H() }).then(J); },
      async sendAgentMessage(id, body, image, file) {
        return fetch(base + "/api/messages", { method: "POST", headers: H(),
          body: JSON.stringify({ conversationId: id, body, image, file }) }).then(J);
      },
      async addNote(id, body) {
        return fetch(base + "/api/conversations/" + id + "/notes", { method: "POST", headers: H(),
          body: JSON.stringify({ body }) }).then(J);
      },
      onChange(cb) { ensureWS(); changeCbs.add(cb); return () => changeCbs.delete(cb); },
      onInbound(cb) { ensureWS(); inboundCbs.add(cb); return () => inboundCbs.delete(cb); },
      onCall(cb) { ensureWS(); callCbs.add(cb); return () => callCbs.delete(cb); },
      sendCall(obj) { wsSend(Object.assign({ type: "call" }, obj)); },
      async widgetStart(opts) {
        const s = await fetch(base + "/widget/session", { method: "POST", headers: H(),
          body: JSON.stringify({ propertySlug: opts.propertySlug, name: opts.name, concern: opts.concern,
            visitorId: pageVisitorId, pageUrl: opts.pageUrl,
            aiMode: opts.aiMode || false, phone: opts.phone || undefined, userId: opts.userId || undefined }) }).then(J);
        if (!s || s.error) return { simulate: true };
        return s;
      },
      async widgetSend(session, text, image, file) {
        return fetch(base + "/widget/messages", { method: "POST", headers: H(),
          body: JSON.stringify({ token: session.token, body: text, image, file }) }).then(J);
      },
      async widgetRequestHuman(session) {
        return fetch(base + "/widget/request-human", { method: "POST", headers: H(),
          body: JSON.stringify({ token: session.token }) }).then(J);
      },
      onWidgetReply(conversationId, cb) { ensureWS(); widgetCbs[conversationId] = cb; wsSend({ type: "watch", conversationId }); return () => { delete widgetCbs[conversationId]; }; },
      async closeConversation(id) { return fetch(base + "/api/conversations/" + id + "/close", { method: "POST", headers: H(), body: "{}" }).then(J); },
      async setConversationAi(id, enabled) { return fetch(base + "/api/conversations/" + id + "/ai", { method: "POST", headers: H(), body: JSON.stringify({ enabled }) }).then(J); },
      async setTags(id, tags) { return fetch(base + "/api/conversations/" + id + "/tags", { method: "POST", headers: H(), body: JSON.stringify({ tags }) }).then(J); },
      async assignConversation(id, agentId) { return fetch(base + "/api/conversations/" + id + "/assign", { method: "POST", headers: H(), body: JSON.stringify({ agentId }) }).then(J); },
      async widgetRate(session, rating) { return fetch(base + "/widget/rate", { method: "POST", headers: H(), body: JSON.stringify({ token: session.token, stars: rating.stars, thumbs: rating.thumbs }) }).then(J); },
      async getSettings() { return fetch(base + "/api/admin/settings", { headers: H() }).then(J); },
      async saveSettings(patch) { return fetch(base + "/api/admin/settings", { method: "PUT", headers: H(), body: JSON.stringify(patch) }).then(J); },
      async getAnalytics() { return fetch(base + "/api/admin/analytics", { headers: H() }).then(J); },
      async adminCreate(kind, obj) { return fetch(base + "/api/admin/" + kind, { method: "POST", headers: H(), body: JSON.stringify(obj) }).then(J); },
      async adminUpdate(kind, id, patch) { return fetch(base + "/api/admin/" + kind + "/" + id, { method: "PATCH", headers: H(), body: JSON.stringify(patch) }).then(J); },
      async adminDelete(kind, id) { return fetch(base + "/api/admin/" + kind + "/" + id, { method: "DELETE", headers: H() }).then(J); },
      async login(email, password) { const r = await fetch(base + "/api/login", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ email, password }) }).then((x) => x.ok ? x.json() : x.json().then((e) => ({ error: e.error }))).catch(() => ({ error: "network" })); if (r && r.token) { token = r.token; try { localStorage.setItem("loop_token", token); } catch (e) {} wsSend({ type: "auth", token }); } return r; },
      async me() { if (!token) return null; return fetch(base + "/api/me", { headers: H() }).then((r) => r.ok ? r.json() : null).then((d) => (d && d.agent) || null).catch(() => null); },
      logout() { const t = token; try { fetch(base + "/api/logout", { method: "POST", headers: { "Content-Type": "application/json", ...(t ? { Authorization: "Bearer " + t } : {}) } }); } catch (e) {} wsSend({ type: "deauth" }); token = null; try { localStorage.removeItem("loop_token"); } catch (e) {} },
    };
    return api;
  })();

  const impl = useLocal ? local : (live || mock);
  window.LoopData = {
    mode: (useLocal || live) ? "live" : "mock",
    setCurrentAgent: (id) => impl.setCurrentAgent(id),
    bootstrap: async function () {
      try { await impl.bootstrap(); this.ready = true; return this.mode; }
      catch (e) {
        console.warn("[Loop] live bootstrap failed, falling back to mock:", e && e.message);
        this.mode = "mock";
        await mock.bootstrap();
        this._impl = mock;
        this.ready = true;
        return this.mode;
      }
    },
    _impl: impl,
    loadConversations: function (f) { return this._impl.loadConversations(f || {}); },
    sendAgentMessage: function (id, b, image, file) { return this._impl.sendAgentMessage(id, b, image, file); },
    onChange: function (cb) { return this._impl.onChange(cb); },
    onInbound: function (cb) { return this._impl.onInbound ? this._impl.onInbound(cb) : function () {}; },
    onCall: function (cb) { return this._impl.onCall ? this._impl.onCall(cb) : function () {}; },
    sendCall: function (obj) { return this._impl.sendCall ? this._impl.sendCall(obj) : undefined; },
    widgetStart: function (o) { return this._impl.widgetStart(o); },
    widgetSend: function (s, t, image, file) { return this._impl.widgetSend(s, t, image, file); },
    addNote: function (id, body) { return (this._impl.addNote ? this._impl.addNote(id, body) : Promise.resolve(null)); },
    closeConversation: function (id) { return this._impl.closeConversation(id); },
    setConversationAi: function (id, enabled) { return this._impl.setConversationAi ? this._impl.setConversationAi(id, enabled) : Promise.resolve(null); },
    widgetRate: function (s, r) { return this._impl.widgetRate(s, r); },
    widgetRequestHuman: function (s) { return this._impl.widgetRequestHuman ? this._impl.widgetRequestHuman(s) : Promise.resolve({ ok: true }); },
    getSettings: function () { return this._impl.getSettings(); },
    saveSettings: function (p) { return this._impl.saveSettings(p); },
    getAnalytics: function () { return this._impl.getAnalytics(); },
    adminCreate: function (k, o) { return this._impl.adminCreate(k, o); },
    adminUpdate: function (k, id, p) { return this._impl.adminUpdate(k, id, p); },
    adminDelete: function (k, id) { return this._impl.adminDelete(k, id); },
    login: function (e, p) { if (this._impl.login) return this._impl.login(e, p); const a = (window.Mock.agents || []).find((x) => x.role === "Admin") || { id: "demo", name: "Demo Admin", role: "Admin", websites: [] }; window.Mock._authAgent = a; return Promise.resolve({ token: "mock", agent: a }); },
    me: function () { if (this._impl.me) return this._impl.me(); return Promise.resolve(window.Mock._authAgent || null); },
    logout: function () { if (this._impl.logout) return this._impl.logout(); window.Mock._authAgent = null; },
    onWidgetReply: function (id, cb) { return this._impl.onWidgetReply(id, cb); },
    setTags: function (id, tags) { return this._impl.setTags ? this._impl.setTags(id, tags) : Promise.resolve(null); },
    assignConversation: function (id, agentId) { return this._impl.assignConversation ? this._impl.assignConversation(id, agentId) : Promise.resolve(null); },
  };
  // backward-compat alias used by PHASE1-WIRING examples
  window.LoopApi = useLocal ? local : (live || mock);
})();
