// ── Symi Glass UI Gateway Client ──────────────────────────────────────
// Connects to the embedded /glass-ws WebSocket bridge inside the gateway.
// The bridge handles auth and protocol translation server-side, keeping
// this client simple.

const SESSION_KEY = "agent:main:main";

// ── Utility ───────────────────────────────────────────────────────────
function extractText(message) {
  if (!message) {
    return "";
  }
  if (typeof message === "string") {
    return message;
  }
  if (Array.isArray(message)) {
    return message
      .filter((b) => b?.type === "text")
      .map((b) => b.text ?? "")
      .join("");
  }
  if (message?.content) {
    return extractText(message.content);
  }
  return "";
}

// ── Gateway Client ────────────────────────────────────────────────────
class SymiGateway extends EventTarget {
  constructor() {
    super();
    this.ws = null;
    this.connected = false;
    this.backoff = 800;
    this._closed = false;
    this._pending = new Map();
    this._rpcSeq = 0;
  }

  start() {
    this._closed = false;
    this._connect();
  }

  stop() {
    this._closed = true;
    this.ws?.close();
    this.ws = null;
    this.connected = false;
    this._flushPending(new Error("gateway client stopped"));
  }

  _connect() {
    if (this._closed) {
      return;
    }

    const protocol = location.protocol === "https:" ? "wss:" : "ws:";
    const wsUrl = `${protocol}//${location.host}/glass-ws`;
    this.ws = new WebSocket(wsUrl);

    this.ws.addEventListener("open", () => {
      this.backoff = 800;
    });

    this.ws.addEventListener("message", (e) => {
      let msg;
      try {
        msg = JSON.parse(e.data);
      } catch {
        return;
      }
      this._handleMessage(msg);
    });

    this.ws.addEventListener("close", () => {
      this.ws = null;
      const wasConnected = this.connected;
      this.connected = false;
      this._flushPending(new Error("gateway closed"));
      if (wasConnected) {
        this.dispatchEvent(new CustomEvent("disconnect"));
      }
      this._scheduleReconnect();
    });

    this.ws.addEventListener("error", () => {});
  }

  _scheduleReconnect() {
    if (this._closed) {
      return;
    }
    const delay = this.backoff;
    this.backoff = Math.min(this.backoff * 1.7, 15000);
    setTimeout(() => this._connect(), delay);
  }

  _flushPending(err) {
    for (const [, p] of this._pending) {
      p.reject(err);
    }
    this._pending.clear();
  }

  _handleMessage(msg) {
    const type = msg.type;

    if (type === "status") {
      const wasConnected = this.connected;
      this.connected = msg.connected;
      if (msg.connected && !wasConnected) {
        this.dispatchEvent(new CustomEvent("connect"));
      }
      return;
    }

    if (type === "history") {
      this.dispatchEvent(
        new CustomEvent("history", {
          detail: {
            messages: msg.messages ?? [],
            total: msg.total ?? 0,
            hasMore: msg.hasMore ?? false,
            startIndex: msg.startIndex ?? 0,
            error: msg.error ?? null,
            recoverable: msg.recoverable === true,
          },
        }),
      );
      return;
    }

    if (type === "chat") {
      this.dispatchEvent(
        new CustomEvent("event", {
          detail: { event: "chat", payload: msg.payload },
        }),
      );
      return;
    }

    if (type === "agent") {
      this.dispatchEvent(
        new CustomEvent("event", {
          detail: { event: "agent", payload: msg.payload },
        }),
      );
      return;
    }

    if (type === "subagent") {
      this.dispatchEvent(
        new CustomEvent("event", {
          detail: { event: "subagent", payload: msg.payload },
        }),
      );
      return;
    }

    if (type === "symframe") {
      // Phase 3 of the symframe arc: server-pushed cards. The payload is
      // { action: "add"|"update"|"remove", id?, card? } and routes to
      // window.symframe.{add,update,remove}. Dispatched via a window-
      // level CustomEvent so symframe.js can subscribe without holding
      // a direct reference to the gateway.
      window.dispatchEvent(new CustomEvent("symi:symframe", { detail: msg.payload }));
      return;
    }

    if (type === "profile") {
      // Model profile broadcast — apply to Glass UI immediately
      if (msg.payload?.profile) {
        window.activeModelProfile = msg.payload.profile;
        window.dispatchEvent(
          new CustomEvent("symi:profile-changed", { detail: msg.payload.profile }),
        );
      }
      return;
    }

    if (type === "rpc.response") {
      const pending = this._pending.get(msg.id);
      if (pending) {
        this._pending.delete(msg.id);
        if (msg.ok) {
          pending.resolve(msg.payload);
        } else {
          pending.reject(new Error(msg.error?.message ?? "rpc failed"));
        }
      }
      return;
    }

    if (type === "error") {
      console.warn("[gateway] Server error:", msg.message);
      return;
    }
  }

  // ── Chat Methods ────────────────────────────────────────────────────
  send(message) {
    if (!this.ws || this.ws.readyState !== WebSocket.OPEN) {
      return Promise.reject(new Error("gateway not connected"));
    }
    this.ws.send(JSON.stringify({ type: "chat.send", message }));
    return Promise.resolve();
  }

  abort(runId) {
    if (!this.ws || this.ws.readyState !== WebSocket.OPEN) {
      return Promise.reject(new Error("gateway not connected"));
    }
    const params = { type: "chat.abort" };
    if (runId) {
      params.runId = runId;
    }
    this.ws.send(JSON.stringify(params));
    return Promise.resolve();
  }

  // ── RPC Helper ──────────────────────────────────────────────────────
  rpc(method, params = {}) {
    if (!this.ws || this.ws.readyState !== WebSocket.OPEN) {
      return Promise.reject(new Error("gateway not connected"));
    }
    const id = String(++this._rpcSeq);
    return new Promise((resolve, reject) => {
      this._pending.set(id, { resolve, reject });
      this.ws.send(JSON.stringify({ type: "rpc", id, method, params }));
      setTimeout(() => {
        if (this._pending.has(id)) {
          this._pending.delete(id);
          reject(new Error("RPC timeout: " + method));
        }
      }, 30000);
    });
  }
}

window.SymiGateway = SymiGateway;
window.SESSION_KEY = SESSION_KEY;
window.extractText = extractText;
