import { readJsonSafe } from "@open-mercato/shared/lib/http/readJsonSafe";
import { fetchWithTimeout, resolveTimeoutMs } from "@open-mercato/shared/lib/http/fetchWithTimeout";
const DEFAULT_OPENCODE_REQUEST_TIMEOUT_MS = 3e4;
const DEFAULT_OPENCODE_SSE_CONNECT_TIMEOUT_MS = 15e3;
function resolveOpencodeTimeoutMs(envVar, fallback) {
  const raw = process.env[envVar];
  const parsed = raw ? Number.parseInt(raw, 10) : void 0;
  return resolveTimeoutMs(parsed, fallback);
}
class OpenCodeClient {
  constructor(config) {
    this.baseUrl = config.baseUrl.replace(/\/$/, "");
    this.headers = {
      "Content-Type": "application/json"
    };
    if (config.password) {
      const credentials = Buffer.from(`opencode:${config.password}`).toString("base64");
      this.headers["Authorization"] = `Basic ${credentials}`;
    }
  }
  /**
   * Subscribe to SSE event stream for real-time updates.
   * Returns an abort function to stop the stream.
   */
  subscribeToEvents(onEvent, onError) {
    const controller = new AbortController();
    const connectTimeoutMs = resolveOpencodeTimeoutMs(
      "OPENCODE_SSE_CONNECT_TIMEOUT_MS",
      DEFAULT_OPENCODE_SSE_CONNECT_TIMEOUT_MS
    );
    const connectTimeoutReason = new Error(
      `OpenCode SSE connection timed out after ${connectTimeoutMs}ms`
    );
    let connectTimer = setTimeout(() => {
      controller.abort(connectTimeoutReason);
    }, connectTimeoutMs);
    const clearConnectTimer = () => {
      if (connectTimer !== null) {
        clearTimeout(connectTimer);
        connectTimer = null;
      }
    };
    const connect = async () => {
      try {
        const res = await fetch(`${this.baseUrl}/event`, {
          headers: {
            ...this.headers,
            Accept: "text/event-stream"
          },
          signal: controller.signal
        });
        clearConnectTimer();
        if (!res.ok || !res.body) {
          throw new Error(`SSE connection failed: ${res.status}`);
        }
        const reader = res.body.getReader();
        const decoder = new TextDecoder();
        let buffer = "";
        while (true) {
          const { done, value } = await reader.read();
          if (done) break;
          buffer += decoder.decode(value, { stream: true });
          const lines = buffer.split("\n");
          buffer = lines.pop() || "";
          for (const line of lines) {
            if (line.startsWith("data: ")) {
              try {
                const data = JSON.parse(line.slice(6));
                onEvent(data);
              } catch {
              }
            }
          }
        }
      } catch (error) {
        clearConnectTimer();
        if (error.name === "AbortError") {
          const reason = controller.signal.reason;
          if (reason === connectTimeoutReason) {
            onError?.(connectTimeoutReason);
          }
          return;
        }
        onError?.(error);
      }
    };
    connect();
    return () => {
      clearConnectTimer();
      controller.abort();
    };
  }
  /**
   * Check OpenCode server health.
   */
  async health() {
    const res = await fetchWithTimeout(`${this.baseUrl}/global/health`, {
      headers: this.headers,
      timeoutMs: resolveOpencodeTimeoutMs("OPENCODE_REQUEST_TIMEOUT_MS", DEFAULT_OPENCODE_REQUEST_TIMEOUT_MS)
    });
    if (!res.ok) {
      throw new Error(`Health check failed: ${res.status}`);
    }
    const data = await readJsonSafe(res, null);
    if (!data) throw new Error("Health check returned invalid JSON response");
    return data;
  }
  /**
   * Get MCP server connection status.
   */
  async mcpStatus() {
    const res = await fetchWithTimeout(`${this.baseUrl}/mcp`, {
      headers: this.headers,
      timeoutMs: resolveOpencodeTimeoutMs("OPENCODE_REQUEST_TIMEOUT_MS", DEFAULT_OPENCODE_REQUEST_TIMEOUT_MS)
    });
    if (!res.ok) {
      throw new Error(`MCP status check failed: ${res.status}`);
    }
    const data = await readJsonSafe(res, null);
    if (!data) throw new Error("MCP status check returned invalid JSON response");
    return data;
  }
  /**
   * Create a new conversation session.
   */
  async createSession() {
    const res = await fetchWithTimeout(`${this.baseUrl}/session`, {
      method: "POST",
      headers: this.headers,
      body: JSON.stringify({}),
      timeoutMs: resolveOpencodeTimeoutMs("OPENCODE_REQUEST_TIMEOUT_MS", DEFAULT_OPENCODE_REQUEST_TIMEOUT_MS)
    });
    if (!res.ok) {
      const error = await res.text();
      throw new Error(`Failed to create session: ${error}`);
    }
    const data = await readJsonSafe(res, null);
    if (!data) throw new Error("Create session returned invalid JSON response");
    return data;
  }
  /**
   * Get an existing session by ID.
   */
  async getSession(sessionId) {
    const res = await fetchWithTimeout(`${this.baseUrl}/session/${sessionId}`, {
      headers: this.headers,
      timeoutMs: resolveOpencodeTimeoutMs("OPENCODE_REQUEST_TIMEOUT_MS", DEFAULT_OPENCODE_REQUEST_TIMEOUT_MS)
    });
    if (!res.ok) {
      throw new Error(`Failed to get session: ${res.status}`);
    }
    const data = await readJsonSafe(res, null);
    if (!data) throw new Error("Get session returned invalid JSON response");
    return data;
  }
  /**
   * Send a message to a session and wait for response.
   */
  async sendMessage(sessionId, message, options) {
    const body = {
      parts: [{ type: "text", text: message }]
    };
    if (options?.model) {
      body.model = options.model;
    }
    const res = await fetchWithTimeout(`${this.baseUrl}/session/${sessionId}/message`, {
      method: "POST",
      headers: this.headers,
      body: JSON.stringify(body),
      timeoutMs: resolveOpencodeTimeoutMs(
        "OPENCODE_SEND_MESSAGE_TIMEOUT_MS",
        resolveOpencodeTimeoutMs("OPENCODE_REQUEST_TIMEOUT_MS", DEFAULT_OPENCODE_REQUEST_TIMEOUT_MS)
      )
    });
    if (!res.ok) {
      const error = await res.text();
      throw new Error(`Failed to send message: ${error}`);
    }
    const data = await readJsonSafe(res, null);
    if (!data) throw new Error("Send message returned invalid JSON response");
    return data;
  }
  /**
   * Set authentication credentials for a provider.
   */
  async setAuth(providerId, apiKey) {
    const res = await fetchWithTimeout(`${this.baseUrl}/auth/${providerId}`, {
      method: "PUT",
      headers: this.headers,
      body: JSON.stringify({ type: "api", key: apiKey }),
      timeoutMs: resolveOpencodeTimeoutMs("OPENCODE_REQUEST_TIMEOUT_MS", DEFAULT_OPENCODE_REQUEST_TIMEOUT_MS)
    });
    if (!res.ok) {
      throw new Error(`Failed to set auth: ${res.status}`);
    }
  }
  /**
   * Get current configuration.
   */
  async getConfig() {
    const res = await fetchWithTimeout(`${this.baseUrl}/config`, {
      headers: this.headers,
      timeoutMs: resolveOpencodeTimeoutMs("OPENCODE_REQUEST_TIMEOUT_MS", DEFAULT_OPENCODE_REQUEST_TIMEOUT_MS)
    });
    if (!res.ok) {
      throw new Error(`Failed to get config: ${res.status}`);
    }
    const data = await readJsonSafe(res, null);
    if (!data) throw new Error("Get config returned invalid JSON response");
    return data;
  }
  /**
   * Get pending questions that need user response.
   */
  async getPendingQuestions() {
    const res = await fetchWithTimeout(`${this.baseUrl}/question`, {
      headers: this.headers,
      timeoutMs: resolveOpencodeTimeoutMs("OPENCODE_REQUEST_TIMEOUT_MS", DEFAULT_OPENCODE_REQUEST_TIMEOUT_MS)
    });
    if (!res.ok) {
      throw new Error(`Failed to get questions: ${res.status}`);
    }
    const data = await readJsonSafe(res, null);
    if (!data) throw new Error("Get questions returned invalid JSON response");
    return data;
  }
  /**
   * Answer a pending question.
   * OpenCode expects: POST /question/{requestID}/reply with { answers: [["label"]] }
   * Each answer is an array of selected option labels (for multi-select support).
   */
  async answerQuestion(questionId, answerIndex) {
    const questions = await this.getPendingQuestions();
    const question = questions.find((q) => q.id === questionId);
    if (!question) {
      throw new Error(`Question ${questionId} not found`);
    }
    const answers = [];
    for (const q of question.questions) {
      const selectedOption = q.options[answerIndex];
      if (selectedOption) {
        answers.push([selectedOption.label]);
      }
    }
    const body = { answers };
    console.log("[OpenCode Client] Answering question", questionId, "with body:", JSON.stringify(body));
    const res = await fetchWithTimeout(`${this.baseUrl}/question/${questionId}/reply`, {
      method: "POST",
      headers: this.headers,
      body: JSON.stringify(body),
      timeoutMs: resolveOpencodeTimeoutMs("OPENCODE_REQUEST_TIMEOUT_MS", DEFAULT_OPENCODE_REQUEST_TIMEOUT_MS)
    });
    const responseText = await res.text();
    console.log("[OpenCode Client] Answer response:", res.status, responseText.substring(0, 200));
    if (!res.ok) {
      throw new Error(`Failed to answer question: ${res.status} - ${responseText}`);
    }
  }
  /**
   * Reject a pending question.
   */
  async rejectQuestion(questionId) {
    console.log("[OpenCode Client] Rejecting question", questionId);
    const res = await fetchWithTimeout(`${this.baseUrl}/question/${questionId}/reject`, {
      method: "POST",
      headers: this.headers,
      timeoutMs: resolveOpencodeTimeoutMs("OPENCODE_REQUEST_TIMEOUT_MS", DEFAULT_OPENCODE_REQUEST_TIMEOUT_MS)
    });
    if (!res.ok) {
      const responseText = await res.text();
      throw new Error(`Failed to reject question: ${res.status} - ${responseText}`);
    }
  }
  /**
   * Get session status (idle, busy, waiting for question).
   * Falls back to inferring status from pending questions if endpoint doesn't exist.
   */
  async getSessionStatus(sessionId) {
    try {
      const res = await fetchWithTimeout(`${this.baseUrl}/session/${sessionId}/status`, {
        headers: this.headers,
        timeoutMs: resolveOpencodeTimeoutMs("OPENCODE_REQUEST_TIMEOUT_MS", DEFAULT_OPENCODE_REQUEST_TIMEOUT_MS)
      });
      if (res.ok) {
        const contentType = res.headers.get("content-type");
        if (contentType && contentType.includes("application/json")) {
          const data = await readJsonSafe(res, null);
          if (data) return data;
        }
      }
    } catch {
    }
    const questions = await this.getPendingQuestions();
    const sessionQuestion = questions.find((q) => q.sessionID === sessionId);
    if (sessionQuestion) {
      return { status: "waiting", questionId: sessionQuestion.id };
    }
    return { status: "unknown" };
  }
}
function createOpenCodeClient(config) {
  return new OpenCodeClient({
    baseUrl: config?.baseUrl ?? process.env.OPENCODE_URL ?? "http://localhost:4096",
    password: config?.password ?? process.env.OPENCODE_PASSWORD
  });
}
export {
  OpenCodeClient,
  createOpenCodeClient
};
//# sourceMappingURL=opencode-client.js.map
