import type {
  BrowserIncomingMessage,
  BrowserOutgoingMessage,
} from "./session-types.js";
import type { CodexAdapter } from "./codex-adapter.js";
import type { Session } from "./ws-bridge-types.js";
import { createLogger } from "./logger.js";

const log = createLogger("ws-bridge");

export interface CodexAttachDeps {
  persistSession: (session: Session) => void;
  refreshGitInfo: (
    session: Session,
    options?: { broadcastUpdate?: boolean; notifyPoller?: boolean },
  ) => Promise<void>;
  broadcastToBrowsers: (session: Session, msg: BrowserIncomingMessage) => void;
  onCLISessionId: ((sessionId: string, cliSessionId: string) => void) | null;
  onFirstTurnCompleted: ((sessionId: string, firstUserMessage: string) => void) | null;
  autoNamingAttempted: Set<string>;
}

export function attachCodexAdapterHandlers(
  sessionId: string,
  session: Session,
  adapter: CodexAdapter,
  deps: CodexAttachDeps,
): void {
  adapter.onBrowserMessage((msg) => {
    if (msg.type === "session_init") {
      session.state = { ...session.state, ...msg.session, backend_type: "codex" };
      void deps.refreshGitInfo(session, { notifyPoller: true });
      // session_init is excluded from sequenceEvent buffering, persist directly.
      deps.persistSession(session);
    } else if (msg.type === "session_update") {
      session.state = { ...session.state, ...msg.session, backend_type: "codex" };
      void deps.refreshGitInfo(session, { notifyPoller: true });
    } else if (msg.type === "status_change") {
      session.state.is_compacting = msg.status === "compacting";
    }

    if (msg.type === "assistant") {
      session.messageHistory.push({ ...msg, timestamp: msg.timestamp || Date.now() });
    } else if (msg.type === "result") {
      session.messageHistory.push(msg);
    }

    if (msg.type === "assistant") {
      const content = (msg as { message?: { content?: Array<{ type: string }> } }).message?.content;
      const hasToolUse = content?.some((b) => b.type === "tool_use");
      if (hasToolUse) {
        log.info({ sessionId: session.id, browserCount: session.browserSockets.size }, "Broadcasting tool_use assistant to browsers");
      }
    }

    if (msg.type === "permission_request") {
      session.pendingPermissions.set(msg.request.request_id, msg.request);
    }

    deps.broadcastToBrowsers(session, msg);

    // Persist explicitly for events that modify meaningful state.
    // Transient events (stream_event, tool_progress, status_change) skip persistence.
    if (
      msg.type === "assistant" ||
      msg.type === "result" ||
      msg.type === "permission_request" ||
      msg.type === "session_update"
    ) {
      deps.persistSession(session);
    }

    if (
      msg.type === "result" &&
      !(msg.data as { is_error?: boolean }).is_error &&
      deps.onFirstTurnCompleted &&
      !deps.autoNamingAttempted.has(session.id)
    ) {
      deps.autoNamingAttempted.add(session.id);
      const firstUserMsg = session.messageHistory.find((m) => m.type === "user_message");
      if (firstUserMsg && firstUserMsg.type === "user_message") {
        deps.onFirstTurnCompleted(session.id, firstUserMsg.content);
      }
    }
  });

  adapter.onSessionMeta((meta) => {
    if (meta.cliSessionId && deps.onCLISessionId) {
      deps.onCLISessionId(session.id, meta.cliSessionId);
    }
    if (meta.model) session.state.model = meta.model;
    if (meta.cwd) session.state.cwd = meta.cwd;
    session.state.backend_type = "codex";
    void deps.refreshGitInfo(session, { broadcastUpdate: true, notifyPoller: true });
    // refreshGitInfo only persists when git info changes. Always persist here
    // to capture model/cwd updates. Debounce in store.save() deduplicates.
    deps.persistSession(session);
  });

  adapter.onDisconnect(() => {
    for (const [reqId] of session.pendingPermissions) {
      deps.broadcastToBrowsers(session, { type: "permission_cancelled", request_id: reqId });
    }
    session.pendingPermissions.clear();
    session.codexAdapter = null;
    log.info({ sessionId }, "Codex adapter disconnected");
    deps.broadcastToBrowsers(session, { type: "cli_disconnected" });
    deps.persistSession(session);
  });

  if (session.pendingMessages.length > 0) {
    log.info({ sessionId, count: session.pendingMessages.length }, "Flushing queued messages to Codex adapter");
    const queued = session.pendingMessages.splice(0);
    for (const raw of queued) {
      try {
        const msg = JSON.parse(raw) as BrowserOutgoingMessage;
        adapter.sendBrowserMessage(msg);
      } catch {
        log.warn({ sessionId, raw: raw.substring(0, 100) }, "Failed to parse queued message for Codex");
      }
    }
  }

  deps.broadcastToBrowsers(session, { type: "cli_connected" });
  log.info({ sessionId }, "Codex adapter attached");
}
