import { getApiKey, getApiUrl } from "./config.js";
// Retry configuration
const RETRY_CONFIG = {
    maxRetries: 3,
    baseDelayMs: 1000,
    maxDelayMs: 10000,
    retryableStatusCodes: [408, 429, 500, 502, 503, 504],
};
// Check if error is retryable
function isRetryableError(error, status) {
    if (status && RETRY_CONFIG.retryableStatusCodes.includes(status)) {
        return true;
    }
    if (error instanceof TypeError)
        return true; // Network errors
    const msg = error instanceof Error ? error.message : String(error);
    return (msg.includes("ECONNRESET") ||
        msg.includes("ETIMEDOUT") ||
        msg.includes("fetch failed"));
}
// Exponential backoff with jitter
function getRetryDelay(attempt) {
    const delay = Math.min(RETRY_CONFIG.baseDelayMs * 2 ** attempt, RETRY_CONFIG.maxDelayMs);
    return Math.round(delay + delay * 0.25 * (Math.random() * 2 - 1));
}
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
// Semaphore for concurrency control
class Semaphore {
    permits;
    queue = [];
    constructor(permits) {
        this.permits = permits;
    }
    async acquire() {
        if (this.permits > 0) {
            this.permits--;
            return;
        }
        return new Promise((resolve) => this.queue.push(resolve));
    }
    release() {
        const next = this.queue.shift();
        if (next)
            next();
        else
            this.permits++;
    }
}
// Limit concurrent requests to 3
const requestSemaphore = new Semaphore(3);
// ============ PRE-AUTH FREE FUNCTIONS ============
// These bypass the singleton + semaphore for unauthenticated/JWT-based requests
export async function signupUser(apiUrl, data) {
    const url = `${apiUrl}/v1/auth/signup`;
    const response = await fetch(url, {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify(data),
    });
    const result = await response.json();
    if (!response.ok) {
        throw new Error(result.error || `Signup failed: ${response.status}`);
    }
    return result;
}
export async function requestWithBearer(apiUrl, bearerToken, method, path, body) {
    const url = `${apiUrl}/v1${path}`;
    const response = await fetch(url, {
        method,
        headers: {
            "Content-Type": "application/json",
            Authorization: `Bearer ${bearerToken}`,
        },
        body: body ? JSON.stringify(body) : undefined,
    });
    const result = await response.json();
    if (!response.ok) {
        throw new Error(result.error || `API error: ${response.status}`);
    }
    return result;
}
export class HarmonyApiClient {
    apiKey;
    apiUrl;
    constructor(options) {
        this.apiKey = options?.apiKey ?? getApiKey();
        this.apiUrl = options?.apiUrl ?? getApiUrl();
    }
    getApiUrl() {
        return this.apiUrl;
    }
    async request(method, path, body, options) {
        await requestSemaphore.acquire();
        try {
            return await this.requestWithRetry(method, path, body, options);
        }
        finally {
            requestSemaphore.release();
        }
    }
    async requestRaw(method, path, body, options) {
        await requestSemaphore.acquire();
        try {
            return await this.requestRawWithRetry(method, path, body, options);
        }
        finally {
            requestSemaphore.release();
        }
    }
    async requestWithRetry(method, path, body, options) {
        const url = `${this.apiUrl}/v1${path}`;
        let lastError = null;
        const contentType = options?.contentType || "application/json";
        const accept = options?.accept || "application/json";
        for (let attempt = 0; attempt <= RETRY_CONFIG.maxRetries; attempt++) {
            try {
                const response = await fetch(url, {
                    method,
                    headers: {
                        "Content-Type": contentType,
                        Accept: accept,
                        "X-API-Key": this.apiKey,
                    },
                    body: options?.rawBody ?? (body ? JSON.stringify(body) : undefined),
                });
                const data = await response.json();
                if (!response.ok) {
                    const errorMsg = data.error || `API error: ${response.status}`;
                    if (!isRetryableError(null, response.status)) {
                        throw new Error(errorMsg);
                    }
                    lastError = new Error(errorMsg);
                    if (attempt < RETRY_CONFIG.maxRetries) {
                        await sleep(getRetryDelay(attempt));
                        continue;
                    }
                    throw lastError;
                }
                return data;
            }
            catch (error) {
                lastError = error instanceof Error ? error : new Error(String(error));
                if (!isRetryableError(error))
                    throw lastError;
                if (attempt < RETRY_CONFIG.maxRetries) {
                    await sleep(getRetryDelay(attempt));
                }
            }
        }
        throw lastError || new Error("Request failed after retries");
    }
    async requestRawWithRetry(method, path, body, options) {
        const url = `${this.apiUrl}/v1${path}`;
        let lastError = null;
        const contentType = options?.contentType || "application/json";
        const accept = options?.accept || "text/markdown";
        for (let attempt = 0; attempt <= RETRY_CONFIG.maxRetries; attempt++) {
            try {
                const response = await fetch(url, {
                    method,
                    headers: {
                        "Content-Type": contentType,
                        Accept: accept,
                        "X-API-Key": this.apiKey,
                    },
                    body: options?.rawBody ?? (body ? JSON.stringify(body) : undefined),
                });
                if (!response.ok) {
                    const text = await response.text();
                    let errorMsg;
                    try {
                        errorMsg =
                            JSON.parse(text).error || `API error: ${response.status}`;
                    }
                    catch {
                        errorMsg = text || `API error: ${response.status}`;
                    }
                    if (!isRetryableError(null, response.status)) {
                        throw new Error(errorMsg);
                    }
                    lastError = new Error(errorMsg);
                    if (attempt < RETRY_CONFIG.maxRetries) {
                        await sleep(getRetryDelay(attempt));
                        continue;
                    }
                    throw lastError;
                }
                return await response.text();
            }
            catch (error) {
                lastError = error instanceof Error ? error : new Error(String(error));
                if (!isRetryableError(error))
                    throw lastError;
                if (attempt < RETRY_CONFIG.maxRetries) {
                    await sleep(getRetryDelay(attempt));
                }
            }
        }
        throw lastError || new Error("Request failed after retries");
    }
    // ============ WORKSPACE OPERATIONS ============
    async listWorkspaces() {
        return this.request("GET", "/workspaces");
    }
    async getWorkspaceMembers(workspaceId) {
        return this.request("GET", `/workspaces/${workspaceId}/members`);
    }
    // ============ PROJECT OPERATIONS ============
    async listProjects(workspaceId) {
        return this.request("GET", `/workspaces/${workspaceId}/projects`);
    }
    async getBoard(projectId, options) {
        const params = new URLSearchParams();
        if (options?.limit !== undefined)
            params.set("limit", String(options.limit));
        if (options?.offset !== undefined)
            params.set("offset", String(options.offset));
        if (options?.columnId)
            params.set("column_id", options.columnId);
        if (options?.summary)
            params.set("summary", "true");
        if (options?.includeArchived)
            params.set("include_archived", "true");
        if (options?.labelName)
            params.set("label_name", options.labelName);
        const query = params.toString() ? `?${params.toString()}` : "";
        return this.request("GET", `/board/${projectId}${query}`);
    }
    // ============ CARD OPERATIONS ============
    async createCard(projectId, data) {
        return this.request("POST", "/cards", { projectId, ...data });
    }
    async updateCard(cardId, updates) {
        return this.request("PATCH", `/cards/${cardId}`, updates);
    }
    async moveCard(cardId, columnId, position) {
        return this.request("POST", `/cards/${cardId}/move`, {
            columnId,
            position,
        });
    }
    async archiveCard(cardId) {
        return this.updateCard(cardId, { archivedAt: new Date().toISOString() });
    }
    async unarchiveCard(cardId) {
        return this.updateCard(cardId, { archivedAt: null });
    }
    async deleteCard(cardId) {
        return this.request("DELETE", `/cards/${cardId}`);
    }
    async getCard(cardId) {
        return this.request("GET", `/cards/${cardId}`);
    }
    async getCardByShortId(projectId, shortId) {
        return this.request("GET", `/projects/${projectId}/cards/${shortId}`);
    }
    async searchCards(query, options) {
        const params = new URLSearchParams({ q: query });
        if (options?.projectId) {
            params.set("project_id", options.projectId);
        }
        return this.request("GET", `/search?${params.toString()}`);
    }
    async addLabelToCard(cardId, labelId) {
        return this.request("POST", `/cards/${cardId}/labels`, { labelId });
    }
    async removeLabelFromCard(cardId, labelId) {
        return this.request("DELETE", `/cards/${cardId}/labels/${labelId}`);
    }
    // ============ CARD LINK OPERATIONS ============
    async addLinkToCard(sourceCardId, targetCardId, linkType) {
        return this.request("POST", `/cards/${sourceCardId}/links`, {
            targetCardId,
            linkType,
        });
    }
    async removeLinkFromCard(linkId) {
        return this.request("DELETE", `/card-links/${linkId}`);
    }
    async getCardLinks(cardId) {
        return this.request("GET", `/cards/${cardId}/links`);
    }
    // ============ COLUMN OPERATIONS ============
    async createColumn(projectId, name) {
        return this.request("POST", "/columns", { projectId, name });
    }
    async updateColumn(columnId, name) {
        return this.request("PATCH", `/columns/${columnId}`, { name });
    }
    async deleteColumn(columnId) {
        return this.request("DELETE", `/columns/${columnId}`);
    }
    // ============ LABEL OPERATIONS ============
    async createLabel(projectId, data) {
        return this.request("POST", "/labels", { projectId, ...data });
    }
    // ============ SUBTASK OPERATIONS ============
    async createSubtask(cardId, title) {
        return this.request("POST", "/subtasks", { cardId, title });
    }
    async toggleSubtask(subtaskId) {
        return this.request("POST", `/subtasks/${subtaskId}/toggle`);
    }
    async deleteSubtask(subtaskId) {
        return this.request("DELETE", `/subtasks/${subtaskId}`);
    }
    // ============ AGENT CONTEXT OPERATIONS ============
    async startAgentSession(cardId, data) {
        return this.request("POST", `/cards/${cardId}/agent-context`, data);
    }
    async updateAgentProgress(cardId, data) {
        return this.request("POST", `/cards/${cardId}/agent-context`, data);
    }
    async endAgentSession(cardId, data) {
        return this.request("DELETE", `/cards/${cardId}/agent-context`, data);
    }
    async getAgentSession(cardId, options) {
        const params = new URLSearchParams();
        if (options?.includeEnded)
            params.set("include_ended", "true");
        const query = params.toString() ? `?${params.toString()}` : "";
        return this.request("GET", `/cards/${cardId}/agent-context${query}`);
    }
    // ============ AGENT PERFORMANCE PROFILES ============
    async getAgentProfile(workspaceId, agentIdentifier) {
        const params = new URLSearchParams({
            workspace_id: workspaceId,
            agent_identifier: agentIdentifier,
        });
        return this.request("GET", `/agent-profiles?${params.toString()}`);
    }
    async listAgentProfiles(workspaceId) {
        const params = new URLSearchParams({ workspace_id: workspaceId });
        return this.request("GET", `/agent-profiles?${params.toString()}`);
    }
    async refreshAgentProfiles(workspaceId) {
        return this.request("POST", "/agent-profiles/refresh", {
            workspace_id: workspaceId,
        });
    }
    // ============ MEMORY OPERATIONS ============
    async createMemoryEntity(data) {
        return this.request("POST", "/memory/entities", data);
    }
    async listMemoryEntities(options) {
        const params = new URLSearchParams();
        params.set("workspace_id", options.workspace_id);
        if (options.project_id)
            params.set("project_id", options.project_id);
        if (options.type)
            params.set("type", options.type);
        if (options.scope)
            params.set("scope", options.scope);
        if (options.tags?.length)
            params.set("tags", options.tags.join(","));
        if (options.agent_identifier)
            params.set("agent_identifier", options.agent_identifier);
        if (options.min_confidence !== undefined)
            params.set("min_confidence", String(options.min_confidence));
        if (options.q)
            params.set("q", options.q);
        if (options.limit !== undefined)
            params.set("limit", String(options.limit));
        if (options.offset !== undefined)
            params.set("offset", String(options.offset));
        return this.request("GET", `/memory/entities?${params.toString()}`);
    }
    async getMemoryEntity(entityId) {
        return this.request("GET", `/memory/entities/${entityId}`);
    }
    async updateMemoryEntity(entityId, updates) {
        return this.request("PUT", `/memory/entities/${entityId}`, updates);
    }
    async deleteMemoryEntity(entityId) {
        return this.request("DELETE", `/memory/entities/${entityId}`);
    }
    async touchMemoryEntity(entityId) {
        return this.request("POST", `/memory/entities/${entityId}/touch`);
    }
    async batchTouchMemoryEntities(entityIds) {
        return this.request("POST", "/memory/entities/batch-touch", {
            entity_ids: entityIds,
        });
    }
    async createMemoryRelation(data) {
        return this.request("POST", "/memory/relations", data);
    }
    async deleteMemoryRelation(relationId) {
        return this.request("DELETE", `/memory/relations/${relationId}`);
    }
    async getRelatedEntities(entityId) {
        return this.request("GET", `/memory/entities/${entityId}/related`);
    }
    async searchMemoryEntities(workspaceId, query, options) {
        const params = new URLSearchParams();
        params.set("workspace_id", workspaceId);
        params.set("q", query);
        if (options?.project_id)
            params.set("project_id", options.project_id);
        if (options?.type)
            params.set("type", options.type);
        if (options?.limit !== undefined)
            params.set("limit", String(options.limit));
        return this.request("GET", `/memory/search?${params.toString()}`);
    }
    // ============ VAULT INDEX ============
    async getVaultIndex(options) {
        const params = new URLSearchParams();
        params.set("workspace_id", options.workspace_id);
        if (options.project_id)
            params.set("project_id", options.project_id);
        if (options.type)
            params.set("type", options.type);
        if (options.limit !== undefined)
            params.set("limit", String(options.limit));
        return this.request("GET", `/memory/index?${params.toString()}`);
    }
    async getVaultIndexMarkdown(options) {
        const params = new URLSearchParams();
        params.set("workspace_id", options.workspace_id);
        if (options.project_id)
            params.set("project_id", options.project_id);
        if (options.type)
            params.set("type", options.type);
        if (options.limit !== undefined)
            params.set("limit", String(options.limit));
        return this.requestRaw("GET", `/memory/index?${params.toString()}`, undefined, {
            accept: "text/markdown",
        });
    }
    // ============ ENTITY STATS (materialized view) ============
    async getMemoryStats(options) {
        const params = new URLSearchParams();
        params.set("workspace_id", options.workspace_id);
        if (options.project_id)
            params.set("project_id", options.project_id);
        return this.request("GET", `/memory/stats?${params.toString()}`);
    }
    async refreshMemoryStats(workspaceId) {
        return this.request("POST", "/memory/stats", { workspace_id: workspaceId });
    }
    // ============ WIKI-LINK RESOLUTION ============
    async resolveLinks(options) {
        return this.request("POST", "/memory/resolve-links", options);
    }
    // ============ MARKDOWN MEMORY OPERATIONS ============
    async listMemoryEntitiesMarkdown(options) {
        const params = new URLSearchParams();
        params.set("workspace_id", options.workspace_id);
        if (options.project_id)
            params.set("project_id", options.project_id);
        if (options.type)
            params.set("type", options.type);
        if (options.scope)
            params.set("scope", options.scope);
        if (options.tags?.length)
            params.set("tags", options.tags.join(","));
        if (options.agent_identifier)
            params.set("agent_identifier", options.agent_identifier);
        if (options.min_confidence !== undefined)
            params.set("min_confidence", String(options.min_confidence));
        if (options.q)
            params.set("q", options.q);
        if (options.limit !== undefined)
            params.set("limit", String(options.limit));
        if (options.offset !== undefined)
            params.set("offset", String(options.offset));
        return this.requestRaw("GET", `/memory/entities?${params.toString()}`, undefined, {
            accept: "text/markdown",
        });
    }
    async getMemoryEntityMarkdown(entityId) {
        return this.requestRaw("GET", `/memory/entities/${entityId}`, undefined, {
            accept: "text/markdown",
        });
    }
    async searchMemoryEntitiesMarkdown(workspaceId, query, options) {
        const params = new URLSearchParams();
        params.set("workspace_id", workspaceId);
        params.set("q", query);
        if (options?.project_id)
            params.set("project_id", options.project_id);
        if (options?.type)
            params.set("type", options.type);
        if (options?.limit !== undefined)
            params.set("limit", String(options.limit));
        return this.requestRaw("GET", `/memory/search?${params.toString()}`, undefined, {
            accept: "text/markdown",
        });
    }
    // ============ EMBEDDING OPERATIONS ============
    async backfillEmbeddings(workspaceId, batchSize) {
        return this.request("POST", "/memory/backfill-embeddings", {
            workspace_id: workspaceId,
            batch_size: batchSize || 50,
        });
    }
    // ============ NLU OPERATIONS ============
    async processNLU(data) {
        return this.request("POST", "/nlu", data);
    }
    // ============ PLAN OPERATIONS ============
    async createPlan(projectId, data) {
        return this.request("POST", "/plans", { projectId, ...data });
    }
    async listPlans(projectId, options) {
        const params = new URLSearchParams({ projectId });
        if (options?.search)
            params.set("search", options.search);
        if (options?.status)
            params.set("status", options.status);
        return this.request("GET", `/plans?${params.toString()}`);
    }
    async getPlan(planId) {
        return this.request("GET", `/plans/${planId}`);
    }
    async getPlanByCardId(cardId) {
        return this.request("GET", `/cards/${cardId}/plan`);
    }
    async updatePlan(planId, updates) {
        return this.request("PATCH", `/plans/${planId}`, updates);
    }
    async updatePlanTask(planId, taskId, updates) {
        return this.request("PATCH", `/plans/${planId}/tasks/${taskId}`, updates);
    }
    // ============ ONBOARDING OPERATIONS ============
    async createWorkspace(data) {
        return this.request("POST", "/workspaces", data);
    }
    async createProject(data) {
        return this.request("POST", "/projects", data);
    }
    async sendInvitations(data) {
        return this.request("POST", "/invitations", data);
    }
    async generateApiKey(name) {
        return this.request("POST", "/api-keys", { name });
    }
}
// Singleton instance
let client = null;
export function getClient() {
    if (!client) {
        client = new HarmonyApiClient();
    }
    return client;
}
export function resetClient() {
    client = null;
}
