import { ApiError, AuthenticationError, NetworkError, } from "./types";
import { AbstractClient } from "./abstract-client";
/**
 * Web client class for interacting with the Axiom Lattice Agent Service API
 */
export class Client extends AbstractClient {
    /**
     * Creates a new Client instance
     * @param config - Configuration options for the client
     */
    constructor(config) {
        super(config);
        this.headers = {
            "Content-Type": "application/json",
            Authorization: `Bearer ${this.config.apiKey}`,
            ...this.config.headers,
        };
    }
    /**
     * Helper method to handle fetch responses and errors
     * @private
     */
    async handleResponse(response) {
        if (!response.ok) {
            if (response.status === 401) {
                throw new AuthenticationError("Authentication failed");
            }
            else {
                const errorData = (await response.json().catch(() => ({})));
                throw new ApiError(errorData.message || "API Error", response.status, errorData);
            }
        }
        // For 204 No Content responses
        if (response.status === 204) {
            return {};
        }
        try {
            return (await response.json());
        }
        catch (error) {
            throw new Error(`Failed to parse response: ${error}`);
        }
    }
    /**
     * Helper method to make fetch requests
     * @private
     */
    async fetchWithTimeout(url, options = {}) {
        const controller = new AbortController();
        const timeoutId = setTimeout(() => controller.abort(), this.config.timeout);
        try {
            const fullUrl = url.startsWith("http")
                ? url
                : `${this.config.baseURL}${url}`;
            const response = await fetch(fullUrl, {
                ...options,
                headers: {
                    ...this.headers,
                    ...(options.headers || {}),
                },
                signal: controller.signal,
            });
            return await this.handleResponse(response);
        }
        catch (error) {
            if (error instanceof DOMException && error.name === "AbortError") {
                throw new NetworkError("Request timed out");
            }
            else if (error instanceof TypeError &&
                error.message.includes("fetch")) {
                throw new NetworkError("Network request failed");
            }
            throw error;
        }
        finally {
            clearTimeout(timeoutId);
        }
    }
    /**
     * Set tenant ID for multi-tenant environments
     * @param tenantId - Tenant identifier
     */
    setTenantId(tenantId) {
        this.tenantId = tenantId;
        this.headers["x-tenant-id"] = tenantId;
    }
    /**
     * Creates a new instance of the client with the given configuration
     * @param config - Configuration options for the client
     * @returns A new Client instance
     */
    createInstance(config) {
        return new Client(config);
    }
    /**
     * Implementation of the abstract makeRequest method for web clients
     * @param url - The URL to make the request to
     * @param options - Request options
     * @returns A promise that resolves to the response data
     */
    async makeRequest(url, options) {
        const method = options?.method || "GET";
        const requestHeaders = {
            ...this.headers,
            ...(options?.headers || {}),
        };
        const requestOptions = {
            method,
            headers: requestHeaders,
        };
        if (options?.body) {
            requestOptions.body = JSON.stringify(options.body);
        }
        return this.fetchWithTimeout(url, requestOptions);
    }
    /**
     * Implementation of the abstract streamRequest method for web clients
     */
    streamRequest(options, onEvent, onComplete, onError) {
        return this.streamRun(options, onEvent, onComplete, onError);
    }
    /**
     * Resume streaming from a known position
     * @param options - Options for resuming the stream
     * @param onEvent - Callback function that receives stream events
     * @param onComplete - Optional callback function called when streaming completes
     * @param onError - Optional callback function called when an error occurs
     * @returns A function that can be called to stop the stream
     */
    resumeStream(options, onEvent, onComplete, onError) {
        const headers = {
            "Content-Type": "application/json",
            Accept: "text/event-stream",
            ...this.headers,
        };
        if (this.tenantId) {
            headers["x-tenant-id"] = this.tenantId;
        }
        // Create abort controller for cancellation
        const controller = new AbortController();
        const { signal } = controller;
        // Start the streaming request
        (async () => {
            try {
                const response = await fetch(`${this.config.baseURL}/api/resume_stream`, {
                    method: "POST",
                    headers,
                    body: JSON.stringify({
                        thread_id: options.threadId,
                        message_id: options.messageId,
                        known_content: options.knownContent,
                        poll_interval: options.pollInterval || 100,
                    }),
                    signal,
                });
                if (!response.ok) {
                    const errorData = (await response.json().catch(() => ({})));
                    throw new Error(errorData.error ||
                        errorData.message ||
                        `HTTP error! Status: ${response.status}`);
                }
                if (!response.body) {
                    throw new Error("Response body is null");
                }
                const reader = response.body.getReader();
                const decoder = new TextDecoder();
                let buffer = "";
                while (true) {
                    const { done, value } = await reader.read();
                    if (done)
                        break;
                    // Decode the chunk and append to buffer
                    const chunk = decoder.decode(value, { stream: true });
                    buffer += chunk;
                    // Process complete lines
                    const lines = buffer.split("\n");
                    // Keep the last potentially incomplete line in the buffer
                    buffer = lines.pop() || "";
                    for (const line of lines) {
                        if (line.trim().startsWith("data: ")) {
                            try {
                                const eventData = JSON.parse(line.trim().slice(6));
                                onEvent(eventData);
                            }
                            catch (error) {
                                console.error("Error parsing SSE data:", line, error);
                                if (onError) {
                                    onError(error instanceof Error ? error : new Error(String(error)));
                                }
                            }
                        }
                    }
                }
                // Process any remaining data in the buffer
                if (buffer && buffer.trim().startsWith("data: ")) {
                    try {
                        const eventData = JSON.parse(buffer.trim().slice(6));
                        onEvent(eventData);
                    }
                    catch (error) {
                        console.error("Error parsing SSE data:", buffer, error);
                    }
                }
                if (onComplete) {
                    onComplete();
                }
            }
            catch (error) {
                if (error instanceof DOMException && error.name === "AbortError") {
                    // Request was cancelled, no need to call onError
                    return;
                }
                if (onError) {
                    onError(error instanceof Error ? error : new Error(String(error)));
                }
            }
        })();
        // Return a function to cancel the fetch request
        return () => {
            controller.abort();
        };
    }
    /**
     * Stream run results
     * @param options - Options for streaming run results
     * @param onEvent - Callback function that receives stream events
     * @param onComplete - Optional callback function called when streaming completes
     * @param onError - Optional callback function called when an error occurs
     * @returns A function that can be called to stop the stream
     */
    streamRun(options, onEvent, onComplete, onError) {
        const headers = {
            "Content-Type": "application/json",
            Accept: "text/event-stream",
            ...this.headers,
        };
        if (this.tenantId) {
            headers["x-tenant-id"] = this.tenantId;
        }
        // Create abort controller for cancellation
        const controller = new AbortController();
        const { signal } = controller;
        // Start the streaming request
        (async () => {
            try {
                // Get request parameters from the abstract method
                const requestParams = this.buildStreamRequestParams(options);
                const response = await fetch(`${this.config.baseURL}${requestParams.url}`, {
                    method: requestParams.method,
                    headers,
                    body: JSON.stringify(requestParams.body),
                    signal,
                });
                if (!response.ok) {
                    const errorData = (await response.json().catch(() => ({})));
                    throw new Error(errorData.error ||
                        errorData.message ||
                        `HTTP error! Status: ${response.status}`);
                }
                if (!response.body) {
                    throw new Error("Response body is null");
                }
                const reader = response.body.getReader();
                const decoder = new TextDecoder();
                let buffer = "";
                while (true) {
                    const { done, value } = await reader.read();
                    if (done)
                        break;
                    // Decode the chunk and append to buffer
                    const chunk = decoder.decode(value, { stream: true });
                    buffer += chunk;
                    // Process complete lines
                    const lines = buffer.split("\n");
                    // Keep the last potentially incomplete line in the buffer
                    buffer = lines.pop() || "";
                    for (const line of lines) {
                        if (line.trim().startsWith("data: ")) {
                            try {
                                const eventData = JSON.parse(line.trim().slice(6));
                                onEvent(eventData);
                            }
                            catch (error) {
                                console.error("Error parsing SSE data:", line, error);
                                if (onError) {
                                    onError(error instanceof Error ? error : new Error(String(error)));
                                }
                            }
                        }
                    }
                }
                // Process any remaining data in the buffer
                if (buffer && buffer.trim().startsWith("data: ")) {
                    try {
                        const eventData = JSON.parse(buffer.trim().slice(6));
                        onEvent(eventData);
                    }
                    catch (error) {
                        console.error("Error parsing SSE data:", buffer, error);
                    }
                }
                if (onComplete) {
                    // Fetch agent state before calling onComplete if enabled
                    if (options.enableReturnStateWhenSteamCompleted) {
                        try {
                            const state = await this.getAgentState(options.threadId);
                            onComplete(state);
                        }
                        catch (error) {
                            // If getting state fails, still call onComplete without state
                            onComplete();
                        }
                    }
                    else {
                        // Just call onComplete without state if not enabled
                        onComplete();
                    }
                }
            }
            catch (error) {
                if (error instanceof DOMException && error.name === "AbortError") {
                    // Request was cancelled, no need to call onError
                    return;
                }
                if (onError) {
                    onError(error instanceof Error ? error : new Error(String(error)));
                }
            }
        })();
        // Return a function to cancel the fetch request
        return () => {
            controller.abort();
        };
    }
}
//# sourceMappingURL=client.js.map