import { getApiKey, getApiUrl } from "./config.js";

export interface ApiResponse<T = unknown> {
  success?: boolean;
  error?: string;
  [key: string]: T | boolean | string | undefined;
}

// 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: unknown, status?: number): boolean {
  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: number): number {
  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: number) => new Promise((resolve) => setTimeout(resolve, ms));

// Semaphore for concurrency control
class Semaphore {
  private permits: number;
  private queue: Array<() => void> = [];

  constructor(permits: number) {
    this.permits = permits;
  }

  async acquire(): Promise<void> {
    if (this.permits > 0) {
      this.permits--;
      return;
    }
    return new Promise((resolve) => this.queue.push(resolve));
  }

  release(): void {
    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: string,
  data: { email: string; password: string; full_name: string },
): Promise<{
  user: { id: string; email: string; full_name: string };
  session: { access_token: string; refresh_token: string; expires_in: number };
}> {
  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<T = unknown>(
  apiUrl: string,
  bearerToken: string,
  method: string,
  path: string,
  body?: unknown,
): Promise<T> {
  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 as T;
}

export class HarmonyApiClient {
  private apiKey: string;
  private apiUrl: string;

  constructor(options?: { apiKey?: string; apiUrl?: string }) {
    this.apiKey = options?.apiKey ?? getApiKey();
    this.apiUrl = options?.apiUrl ?? getApiUrl();
  }

  getApiUrl(): string {
    return this.apiUrl;
  }

  private async request<T>(
    method: string,
    path: string,
    body?: unknown,
    options?: { accept?: string; contentType?: string; rawBody?: string },
  ): Promise<T> {
    await requestSemaphore.acquire();
    try {
      return await this.requestWithRetry<T>(method, path, body, options);
    } finally {
      requestSemaphore.release();
    }
  }

  private async requestRaw(
    method: string,
    path: string,
    body?: unknown,
    options?: { accept?: string; contentType?: string; rawBody?: string },
  ): Promise<string> {
    await requestSemaphore.acquire();
    try {
      return await this.requestRawWithRetry(method, path, body, options);
    } finally {
      requestSemaphore.release();
    }
  }

  private async requestWithRetry<T>(
    method: string,
    path: string,
    body?: unknown,
    options?: { accept?: string; contentType?: string; rawBody?: string },
  ): Promise<T> {
    const url = `${this.apiUrl}/v1${path}`;
    let lastError: Error | null = 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 as T;
      } 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");
  }

  private async requestRawWithRetry(
    method: string,
    path: string,
    body?: unknown,
    options?: { accept?: string; contentType?: string; rawBody?: string },
  ): Promise<string> {
    const url = `${this.apiUrl}/v1${path}`;
    let lastError: Error | null = 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: string;
          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(): Promise<{ workspaces: unknown[] }> {
    return this.request("GET", "/workspaces");
  }

  async getWorkspaceMembers(
    workspaceId: string,
  ): Promise<{ members: unknown[] }> {
    return this.request("GET", `/workspaces/${workspaceId}/members`);
  }

  // ============ PROJECT OPERATIONS ============

  async listProjects(workspaceId: string): Promise<{ projects: unknown[] }> {
    return this.request("GET", `/workspaces/${workspaceId}/projects`);
  }

  async getBoard(
    projectId: string,
    options?: {
      limit?: number;
      offset?: number;
      columnId?: string;
      summary?: boolean;
      includeArchived?: boolean;
      labelName?: string;
    },
  ): Promise<{
    project: unknown;
    columns: unknown[];
    cards?: unknown[];
    labels: unknown[];
    totalCards?: number;
    pagination?: {
      limit: number;
      offset: number;
      totalCards: number;
      hasMore: boolean;
    };
    summary?: boolean;
  }> {
    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: string,
    data: {
      title: string;
      columnId?: string;
      description?: string;
      priority?: string;
      assigneeId?: string;
    },
  ): Promise<{ card: unknown }> {
    return this.request("POST", "/cards", { projectId, ...data });
  }

  async updateCard(
    cardId: string,
    updates: {
      title?: string;
      description?: string;
      priority?: string;
      assigneeId?: string | null;
      dueDate?: string | null;
      done?: boolean;
      archivedAt?: string | null;
    },
  ): Promise<{ card: unknown }> {
    return this.request("PATCH", `/cards/${cardId}`, updates);
  }

  async moveCard(
    cardId: string,
    columnId: string,
    position?: number,
  ): Promise<{ card: unknown }> {
    return this.request("POST", `/cards/${cardId}/move`, {
      columnId,
      position,
    });
  }

  async archiveCard(cardId: string): Promise<{ card: unknown }> {
    return this.updateCard(cardId, { archivedAt: new Date().toISOString() });
  }

  async unarchiveCard(cardId: string): Promise<{ card: unknown }> {
    return this.updateCard(cardId, { archivedAt: null });
  }

  async deleteCard(cardId: string): Promise<{ success: boolean }> {
    return this.request("DELETE", `/cards/${cardId}`);
  }

  async getCard(cardId: string): Promise<{ card: unknown }> {
    return this.request("GET", `/cards/${cardId}`);
  }

  async getCardByShortId(
    projectId: string,
    shortId: number,
  ): Promise<{ card: unknown }> {
    return this.request("GET", `/projects/${projectId}/cards/${shortId}`);
  }

  async searchCards(
    query: string,
    options?: { projectId?: string },
  ): Promise<{ cards: unknown[] }> {
    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: string,
    labelId: string,
  ): Promise<{ success: boolean }> {
    return this.request("POST", `/cards/${cardId}/labels`, { labelId });
  }

  async removeLabelFromCard(
    cardId: string,
    labelId: string,
  ): Promise<{ success: boolean }> {
    return this.request("DELETE", `/cards/${cardId}/labels/${labelId}`);
  }

  // ============ CARD LINK OPERATIONS ============

  async addLinkToCard(
    sourceCardId: string,
    targetCardId: string,
    linkType: "relates_to" | "blocks" | "duplicates" | "is_part_of",
  ): Promise<{ link: unknown }> {
    return this.request("POST", `/cards/${sourceCardId}/links`, {
      targetCardId,
      linkType,
    });
  }

  async removeLinkFromCard(linkId: string): Promise<{ success: boolean }> {
    return this.request("DELETE", `/card-links/${linkId}`);
  }

  async getCardLinks(cardId: string): Promise<{ links: unknown[] }> {
    return this.request("GET", `/cards/${cardId}/links`);
  }

  // ============ COLUMN OPERATIONS ============

  async createColumn(
    projectId: string,
    name: string,
  ): Promise<{ column: unknown }> {
    return this.request("POST", "/columns", { projectId, name });
  }

  async updateColumn(
    columnId: string,
    name: string,
  ): Promise<{ column: unknown }> {
    return this.request("PATCH", `/columns/${columnId}`, { name });
  }

  async deleteColumn(columnId: string): Promise<{ success: boolean }> {
    return this.request("DELETE", `/columns/${columnId}`);
  }

  // ============ LABEL OPERATIONS ============

  async createLabel(
    projectId: string,
    data: { name: string; color: string },
  ): Promise<{ label: unknown }> {
    return this.request("POST", "/labels", { projectId, ...data });
  }

  // ============ SUBTASK OPERATIONS ============

  async createSubtask(
    cardId: string,
    title: string,
  ): Promise<{ subtask: unknown }> {
    return this.request("POST", "/subtasks", { cardId, title });
  }

  async toggleSubtask(subtaskId: string): Promise<{ subtask: unknown }> {
    return this.request("POST", `/subtasks/${subtaskId}/toggle`);
  }

  async deleteSubtask(subtaskId: string): Promise<{ success: boolean }> {
    return this.request("DELETE", `/subtasks/${subtaskId}`);
  }

  // ============ AGENT CONTEXT OPERATIONS ============

  async startAgentSession(
    cardId: string,
    data: {
      agentIdentifier: string;
      agentName: string;
      status?: "working" | "blocked" | "paused" | "completed";
      progressPercent?: number;
      currentTask?: string;
      blockers?: string[];
      estimatedMinutesRemaining?: number;
    },
  ): Promise<{ session: unknown; created: boolean }> {
    return this.request("POST", `/cards/${cardId}/agent-context`, data);
  }

  async updateAgentProgress(
    cardId: string,
    data: {
      agentIdentifier: string;
      agentName: string;
      status?: "working" | "blocked" | "paused" | "completed";
      progressPercent?: number;
      currentTask?: string;
      blockers?: string[];
      estimatedMinutesRemaining?: number;
      phase?: string;
      filesChanged?: number;
      costCents?: number;
      recentActions?: { action: string; ts: string }[];
    },
  ): Promise<{ session: unknown; created: boolean }> {
    return this.request("POST", `/cards/${cardId}/agent-context`, data);
  }

  async endAgentSession(
    cardId: string,
    data?: {
      status?: "completed" | "paused";
      progressPercent?: number;
    },
  ): Promise<{ session: unknown }> {
    return this.request("DELETE", `/cards/${cardId}/agent-context`, data);
  }

  async getAgentSession(
    cardId: string,
    options?: { includeEnded?: boolean },
  ): Promise<{ session?: unknown; sessions?: unknown[] }> {
    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: string,
    agentIdentifier: string,
  ): Promise<{ profile: unknown }> {
    const params = new URLSearchParams({
      workspace_id: workspaceId,
      agent_identifier: agentIdentifier,
    });
    return this.request("GET", `/agent-profiles?${params.toString()}`);
  }

  async listAgentProfiles(
    workspaceId: string,
  ): Promise<{ profiles: unknown[] }> {
    const params = new URLSearchParams({ workspace_id: workspaceId });
    return this.request("GET", `/agent-profiles?${params.toString()}`);
  }

  async refreshAgentProfiles(
    workspaceId: string,
  ): Promise<{ refreshed: boolean }> {
    return this.request("POST", "/agent-profiles/refresh", {
      workspace_id: workspaceId,
    });
  }

  // ============ MEMORY OPERATIONS ============

  async createMemoryEntity(data: {
    workspace_id: string;
    project_id?: string;
    type: string;
    scope?: string;
    memory_tier?: string;
    title: string;
    content: string;
    metadata?: Record<string, unknown>;
    confidence?: number;
    tags?: string[];
    agent_identifier?: string;
  }): Promise<{ entity: unknown; warnings?: string[] }> {
    return this.request("POST", "/memory/entities", data);
  }

  async listMemoryEntities(options: {
    workspace_id: string;
    project_id?: string;
    type?: string;
    scope?: string;
    tags?: string[];
    agent_identifier?: string;
    min_confidence?: number;
    q?: string;
    limit?: number;
    offset?: number;
  }): Promise<{ entities: unknown[]; count: number }> {
    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: string): Promise<{ entity: unknown }> {
    return this.request("GET", `/memory/entities/${entityId}`);
  }

  async updateMemoryEntity(
    entityId: string,
    updates: {
      title?: string;
      content?: string;
      metadata?: Record<string, unknown>;
      confidence?: number;
      tags?: string[];
      scope?: string;
      type?: string;
      memory_tier?: string;
    },
  ): Promise<{ entity: unknown; warnings?: string[] }> {
    return this.request("PUT", `/memory/entities/${entityId}`, updates);
  }

  async deleteMemoryEntity(entityId: string): Promise<{ success: boolean }> {
    return this.request("DELETE", `/memory/entities/${entityId}`);
  }

  async touchMemoryEntity(entityId: string): Promise<{ success: boolean }> {
    return this.request("POST", `/memory/entities/${entityId}/touch`);
  }

  async batchTouchMemoryEntities(
    entityIds: string[],
  ): Promise<{ success: boolean; count: number }> {
    return this.request("POST", "/memory/entities/batch-touch", {
      entity_ids: entityIds,
    });
  }

  async createMemoryRelation(data: {
    source_id: string;
    target_id: string;
    relation_type: string;
    confidence?: number;
  }): Promise<{ relation: unknown }> {
    return this.request("POST", "/memory/relations", data);
  }

  async deleteMemoryRelation(
    relationId: string,
  ): Promise<{ success: boolean }> {
    return this.request("DELETE", `/memory/relations/${relationId}`);
  }

  async getRelatedEntities(
    entityId: string,
  ): Promise<{ outgoing: unknown[]; incoming: unknown[] }> {
    return this.request("GET", `/memory/entities/${entityId}/related`);
  }

  async searchMemoryEntities(
    workspaceId: string,
    query: string,
    options?: {
      project_id?: string;
      type?: string;
      limit?: number;
    },
  ): Promise<{ entities: unknown[]; count: number }> {
    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: {
    workspace_id: string;
    project_id?: string;
    type?: string;
    limit?: number;
  }): Promise<{ entities: unknown[]; count: number }> {
    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: {
    workspace_id: string;
    project_id?: string;
    type?: string;
    limit?: number;
  }): Promise<string> {
    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: {
    workspace_id: string;
    project_id?: string;
  }): Promise<{ stats: unknown[] }> {
    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: string): Promise<{ success: boolean }> {
    return this.request("POST", "/memory/stats", { workspace_id: workspaceId });
  }

  // ============ WIKI-LINK RESOLUTION ============

  async resolveLinks(options: {
    workspace_id: string;
    project_id?: string;
  }): Promise<{ resolved: number; unresolved: number }> {
    return this.request("POST", "/memory/resolve-links", options);
  }

  // ============ MARKDOWN MEMORY OPERATIONS ============

  async listMemoryEntitiesMarkdown(options: {
    workspace_id: string;
    project_id?: string;
    type?: string;
    scope?: string;
    tags?: string[];
    agent_identifier?: string;
    min_confidence?: number;
    q?: string;
    limit?: number;
    offset?: number;
  }): Promise<string> {
    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: string): Promise<string> {
    return this.requestRaw("GET", `/memory/entities/${entityId}`, undefined, {
      accept: "text/markdown",
    });
  }

  async searchMemoryEntitiesMarkdown(
    workspaceId: string,
    query: string,
    options?: {
      project_id?: string;
      type?: string;
      limit?: number;
    },
  ): Promise<string> {
    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: string,
    batchSize?: number,
  ): Promise<{
    processed: number;
    remaining: number;
    errors: Array<{ entity_id: string; error: string }>;
  }> {
    return this.request("POST", "/memory/backfill-embeddings", {
      workspace_id: workspaceId,
      batch_size: batchSize || 50,
    });
  }

  // ============ NLU OPERATIONS ============

  async processNLU(data: {
    command: string;
    projectId?: string;
    workspaceId?: string;
    language?: string;
    execute?: boolean;
  }): Promise<unknown> {
    return this.request("POST", "/nlu", data);
  }

  // ============ PLAN OPERATIONS ============

  async createPlan(
    projectId: string,
    data: {
      title: string;
      content?: string;
      source?: "user" | "agent" | "imported";
      tasks?: Array<{
        content: string;
        priority?: "high" | "medium" | "low";
        status?: "pending" | "in_progress" | "completed";
      }>;
    },
  ): Promise<{ plan: unknown; tasks?: unknown[] }> {
    return this.request("POST", "/plans", { projectId, ...data });
  }

  async listPlans(
    projectId: string,
    options?: {
      search?: string;
      status?: string;
    },
  ): Promise<{ plans: unknown[] }> {
    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: string): Promise<{
    plan: unknown;
    tasks: unknown[];
  }> {
    return this.request("GET", `/plans/${planId}`);
  }

  async getPlanByCardId(cardId: string): Promise<{
    plan: unknown;
    tasks: unknown[];
  } | null> {
    return this.request("GET", `/cards/${cardId}/plan`);
  }

  async updatePlan(
    planId: string,
    updates: {
      title?: string;
      content?: string;
      status?: "draft" | "active" | "archived";
    },
  ): Promise<{ plan: unknown }> {
    return this.request("PATCH", `/plans/${planId}`, updates);
  }

  async updatePlanTask(
    planId: string,
    taskId: string,
    updates: {
      cardId?: string;
      status?: "pending" | "in_progress" | "completed";
    },
  ): Promise<{ task: unknown }> {
    return this.request("PATCH", `/plans/${planId}/tasks/${taskId}`, updates);
  }

  // ============ ONBOARDING OPERATIONS ============

  async createWorkspace(data: { name: string; description?: string }): Promise<{
    workspace: { id: string; name: string; slug: string; created_at: string };
  }> {
    return this.request("POST", "/workspaces", data);
  }

  async createProject(data: {
    workspaceId: string;
    name: string;
    description?: string;
    color?: string;
    template?: string;
  }): Promise<{
    project: { id: string; name: string; slug: string };
    columns: unknown[];
  }> {
    return this.request("POST", "/projects", data);
  }

  async sendInvitations(data: {
    workspaceId: string;
    emails: string[];
    role?: string;
    sendEmail?: boolean;
  }): Promise<{
    results: {
      email: string;
      success: boolean;
      invitationId?: string;
      error?: string;
    }[];
    totalSent: number;
    totalFailed: number;
  }> {
    return this.request("POST", "/invitations", data);
  }

  async generateApiKey(name: string): Promise<{
    apiKey: { id: string; name: string; prefix: string; created_at: string };
    rawKey: string;
    warning: string;
  }> {
    return this.request("POST", "/api-keys", { name });
  }
}

// Singleton instance
let client: HarmonyApiClient | null = null;

export function getClient(): HarmonyApiClient {
  if (!client) {
    client = new HarmonyApiClient();
  }
  return client;
}

export function resetClient(): void {
  client = null;
}
