// Bundled entry point for the macOS WKWebView web chat.
// This replaces the inline module script in index.html so we can ship a single JS bundle.

/* global window, document */

if (!globalThis.process) {
  // Some vendor modules peek at process.env; provide a minimal stub for browser.
  globalThis.process = { env: {} };
}

const logStatus = (msg) => {
  try {
    console.log(msg);
    const el = document.getElementById("app");
    if (el && !el.dataset.booted) el.textContent = msg;
  } catch {
    // Ignore logging failures—never block bootstrap.
  }
};

async function fetchBootstrap() {
  const params = new URLSearchParams(window.location.search);
  const sessionKey = params.get("session") || "main";
  const infoUrl = new URL(`./info?session=${encodeURIComponent(sessionKey)}`, window.location.href);
  const infoResp = await fetch(infoUrl, { credentials: "omit" });
  if (!infoResp.ok) {
    throw new Error(`webchat info failed (${infoResp.status})`);
  }
  const info = await infoResp.json();
  return {
    sessionKey,
    basePath: info.basePath || "/webchat/",
    initialMessages: Array.isArray(info.initialMessages) ? info.initialMessages : [],
    thinkingLevel: typeof info.thinkingLevel === "string" ? info.thinkingLevel : "off",
  };
}

function latestTimestamp(messages) {
  if (!Array.isArray(messages) || messages.length === 0) return 0;
  const withTs = messages.filter((m) => typeof m?.timestamp === "number");
  if (withTs.length === 0) return messages.length; // best-effort monotonic fallback
  return withTs[withTs.length - 1].timestamp;
}

class NativeTransport {
  constructor(sessionKey) {
    this.sessionKey = sessionKey;
  }

  async *run(messages, userMessage, cfg, signal) {
    const attachments = userMessage.attachments?.map((a) => ({
      type: a.type,
      mimeType: a.mimeType,
      fileName: a.fileName,
      content:
        typeof a.content === "string"
          ? a.content
          : btoa(String.fromCharCode(...new Uint8Array(a.content))),
    }));
    const rpcUrl = new URL("./rpc", window.location.href);
    const rpcBody = {
      text: userMessage.content?.[0]?.text ?? "",
      session: this.sessionKey,
      attachments,
    };
    if (cfg?.thinkingOnce) {
      rpcBody.thinkingOnce = cfg.thinkingOnce;
    } else if (cfg?.thinkingOverride) {
      rpcBody.thinking = cfg.thinkingOverride;
    }
    const resultResp = await fetch(rpcUrl, {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify(rpcBody),
      signal,
    });

    if (!resultResp.ok) {
      throw new Error(`rpc failed (${resultResp.status})`);
    }
    const body = await resultResp.json();
    if (!body.ok) {
      throw new Error(body.error || "rpc error");
    }
    const first = Array.isArray(body.payloads) ? body.payloads[0] : undefined;
    const text = (first?.text ?? "").toString();

    const usage = {
      input: 0,
      output: 0,
      cacheRead: 0,
      cacheWrite: 0,
      cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },
    };
    const assistant = {
      role: "assistant",
      content: [{ type: "text", text }],
      api: cfg.model.api,
      provider: cfg.model.provider,
      model: cfg.model.id,
      usage,
      stopReason: "stop",
      timestamp: Date.now(),
    };
    yield { type: "turn_start" };
    yield { type: "message_start", message: assistant };
    yield { type: "message_end", message: assistant };
    yield { type: "turn_end" };
    yield { type: "agent_end" };
  }
}

const startChat = async () => {
  logStatus("boot: fetching session info");
  const { initialMessages, sessionKey, thinkingLevel } = await fetchBootstrap();

  logStatus("boot: starting imports");
  const { Agent } = await import("./agent/agent.js");
  const { ChatPanel } = await import("./ChatPanel.js");
  const { AppStorage, setAppStorage } = await import("./storage/app-storage.js");
  const { SettingsStore } = await import("./storage/stores/settings-store.js");
  const { ProviderKeysStore } = await import("./storage/stores/provider-keys-store.js");
  const { SessionsStore } = await import("./storage/stores/sessions-store.js");
  const { CustomProvidersStore } = await import("./storage/stores/custom-providers-store.js");
  const { IndexedDBStorageBackend } = await import("./storage/backends/indexeddb-storage-backend.js");
  const { getModel } = await import("@mariozechner/pi-ai");
  logStatus("boot: modules loaded");

  // Initialize storage with an in-browser IndexedDB backend.
  const backend = new IndexedDBStorageBackend({
    dbName: "clawdis-webchat",
    version: 1,
    stores: [
      new SettingsStore().getConfig(),
      new ProviderKeysStore().getConfig(),
      new SessionsStore().getConfig(),
      SessionsStore.getMetadataConfig(),
      new CustomProvidersStore().getConfig(),
    ],
  });
  const settingsStore = new SettingsStore();
  const providerKeysStore = new ProviderKeysStore();
  const sessionsStore = new SessionsStore();
  const customProvidersStore = new CustomProvidersStore();

  for (const store of [settingsStore, providerKeysStore, sessionsStore, customProvidersStore]) {
    store.setBackend(backend);
  }

  const storage = new AppStorage(
    settingsStore,
    providerKeysStore,
    sessionsStore,
    customProvidersStore,
    backend,
  );
  setAppStorage(storage);

  // Prepopulate a dummy API key so the UI does not block sends in embedded mode.
  const defaultProvider = "anthropic";
  try {
    await providerKeysStore.set(defaultProvider, "embedded");
  } catch (err) {
    logStatus(`storage warn: could not seed provider key: ${err}`);
  }

  const agent = new Agent({
    initialState: {
      systemPrompt: "You are Clawd (primary session).",
      model: getModel("anthropic", "claude-opus-4-5"),
      thinkingLevel,
      messages: initialMessages,
    },
    transport: new NativeTransport(sessionKey),
  });

  const origPrompt = agent.prompt.bind(agent);
  agent.prompt = async (input, attachments) => {
    const userMessage = {
      role: "user",
      content: [{ type: "text", text: input }],
      attachments: attachments?.length ? attachments : undefined,
      timestamp: Date.now(),
    };
    agent.appendMessage(userMessage);
    return origPrompt(input, attachments);
  };

  const panel = new ChatPanel();
  panel.style.height = "100%";
  panel.style.display = "block";
  await panel.setAgent(agent, { sessionThinkingLevel: thinkingLevel });

  const mount = document.getElementById("app");
  if (!mount) throw new Error("#app container missing");
  mount.dataset.booted = "1";
  mount.textContent = "";
  mount.appendChild(panel);
  logStatus("boot: ready");

  // Live sync via WebSocket so other transports (WhatsApp/CLI) appear instantly.
  let lastSyncedTs = latestTimestamp(initialMessages);
  let ws;
  let reconnectTimer;

  const applySnapshot = (info) => {
    const messages = Array.isArray(info?.messages) ? info.messages : [];
    const ts = latestTimestamp(messages);
    const thinking = typeof info?.thinkingLevel === "string" ? info.thinkingLevel : "off";

    if (!agent.state.isStreaming && ts && ts !== lastSyncedTs) {
      agent.replaceMessages(messages);
      lastSyncedTs = ts;
    }

    if (thinking && thinking !== agent.state.thinkingLevel) {
      agent.setThinkingLevel(thinking);
      if (panel?.agentInterface) {
        panel.agentInterface.sessionThinkingLevel = thinking;
        panel.agentInterface.pendingThinkingLevel = null;
        if (panel.agentInterface._messageEditor) {
          panel.agentInterface._messageEditor.thinkingLevel = thinking;
        }
      }
    }
  };

  const connectSocket = () => {
    try {
      const wsUrl = new URL(`./socket?session=${encodeURIComponent(sessionKey)}`, window.location.href);
      wsUrl.protocol = wsUrl.protocol.replace("http", "ws");
      ws = new WebSocket(wsUrl);

      ws.onmessage = (ev) => {
        try {
          const data = JSON.parse(ev.data);
          if (data?.type === "session") applySnapshot(data);
        } catch (err) {
          console.warn("ws message parse failed", err);
        }
      };

      ws.onclose = () => {
        ws = null;
        if (!reconnectTimer) {
          reconnectTimer = setTimeout(() => {
            reconnectTimer = null;
            connectSocket();
          }, 2000);
        }
      };

      ws.onerror = () => {
        ws?.close();
      };
    } catch (err) {
      console.warn("ws connect failed", err);
    }
  };

  connectSocket();
};

startChat().catch((err) => {
  const msg = err?.stack || err?.message || String(err);
  logStatus(`boot failed: ${msg}`);
  document.body.style.color = "#e06666";
  document.body.style.fontFamily = "monospace";
  document.body.style.padding = "16px";
  document.body.innerText = "Web chat failed to load:\\n" + msg;
});
