import { supabase } from "./supabase";

const API_CONFIG = {
  local: {
    http: "http://localhost:8080",
    ws: "ws://localhost:8080",
  },
  production: {
    http: "https://api.makima.jp",
    ws: "wss://api.makima.jp",
  },
} as const;

type Environment = "local" | "production";

function detectEnvironment(): Environment {
  // Check if explicitly set via env var
  const envOverride = import.meta.env.VITE_API_ENV as Environment | undefined;
  if (envOverride && (envOverride === "local" || envOverride === "production")) {
    return envOverride;
  }

  // Auto-detect based on hostname
  if (typeof window !== "undefined") {
    const hostname = window.location.hostname;
    if (hostname === "localhost" || hostname === "127.0.0.1") {
      return "local";
    }
  }

  return "production";
}

const env = detectEnvironment();

export const API_BASE = API_CONFIG[env].http;
export const WS_BASE = API_CONFIG[env].ws;

// =============================================================================
// Authentication helpers
// =============================================================================

/** Storage key for API key */
const API_KEY_STORAGE_KEY = "makima_api_key";

/** Get stored API key from localStorage */
export function getStoredApiKey(): string | null {
  if (typeof window === "undefined") return null;
  return localStorage.getItem(API_KEY_STORAGE_KEY);
}

/** Store API key in localStorage */
export function setStoredApiKey(key: string): void {
  if (typeof window === "undefined") return;
  localStorage.setItem(API_KEY_STORAGE_KEY, key);
}

/** Remove stored API key */
export function clearStoredApiKey(): void {
  if (typeof window === "undefined") return;
  localStorage.removeItem(API_KEY_STORAGE_KEY);
}

/** Get auth headers for API requests */
async function getAuthHeaders(): Promise<HeadersInit> {
  const headers: HeadersInit = {
    "Content-Type": "application/json",
  };

  // Try Supabase session first
  if (supabase) {
    const { data: { session } } = await supabase.auth.getSession();
    if (session?.access_token) {
      headers["Authorization"] = `Bearer ${session.access_token}`;
      return headers;
    }
  }

  // Fall back to API key if available
  const apiKey = getStoredApiKey();
  if (apiKey) {
    headers["X-Makima-API-Key"] = apiKey;
  }

  return headers;
}

/** Fetch with authentication headers */
async function authFetch(url: string, options: RequestInit = {}): Promise<Response> {
  const authHeaders = await getAuthHeaders();
  const mergedHeaders = {
    ...authHeaders,
    ...options.headers,
  };

  return fetch(url, {
    ...options,
    headers: mergedHeaders,
  });
}
export const LISTEN_ENDPOINT = `${WS_BASE}/api/v1/listen`;
export const FILE_SUBSCRIBE_ENDPOINT = `${WS_BASE}/api/v1/files/subscribe`;
export const TASK_SUBSCRIBE_ENDPOINT = `${WS_BASE}/api/v1/mesh/tasks/subscribe`;

export function getEnvironment(): Environment {
  return env;
}

// File API types
export interface TranscriptEntry {
  id: string;
  speaker: string;
  start: number;
  end: number;
  text: string;
  isFinal: boolean;
}

// Chart types for visualization
export type ChartType = "line" | "bar" | "pie" | "area";

// Body element types for structured content
export type BodyElement =
  | { type: "heading"; level: number; text: string }
  | { type: "paragraph"; text: string }
  | { type: "code"; language?: string; content: string }
  | { type: "list"; ordered: boolean; items: string[] }
  | {
      type: "chart";
      chartType: ChartType;
      title?: string;
      data: Record<string, unknown>[];
      config?: Record<string, unknown>;
    }
  | { type: "image"; src: string; alt?: string; caption?: string }
  | { type: "markdown"; content: string };

export interface FileSummary {
  id: string;
  name: string;
  description: string | null;
  transcriptCount: number;
  duration: number | null;
  version: number;
  /** Path to linked repository file (e.g., "README.md") */
  repoFilePath: string | null;
  /** Sync status: 'none', 'synced', 'modified', 'conflict' */
  repoSyncStatus: 'none' | 'synced' | 'modified' | 'conflict' | null;
  createdAt: string;
  updatedAt: string;
  // Contract info (joined from contracts table)
  contractId: string | null;
  contractName: string | null;
  contractPhase: ContractPhase | null;
}

export interface FileDetail {
  id: string;
  ownerId: string;
  name: string;
  description: string | null;
  transcript: TranscriptEntry[];
  location: string | null;
  summary: string | null;
  body: BodyElement[];
  version: number;
  /** Path to linked repository file (e.g., "README.md") */
  repoFilePath: string | null;
  /** When file was last synced from repository */
  repoSyncedAt: string | null;
  /** Sync status: 'none', 'synced', 'modified', 'conflict' */
  repoSyncStatus: 'none' | 'synced' | 'modified' | 'conflict' | null;
  createdAt: string;
  updatedAt: string;
}

export interface FileListResponse {
  files: FileSummary[];
  total: number;
}

export interface CreateFileRequest {
  /** Contract this file belongs to (required - files must belong to a contract) */
  contractId: string;
  name?: string;
  description?: string;
  transcript?: TranscriptEntry[];
  location?: string;
  /** Initial body elements (e.g., from a template) */
  body?: BodyElement[];
}

export interface UpdateFileRequest {
  name?: string;
  description?: string;
  transcript?: TranscriptEntry[];
  summary?: string;
  body?: BodyElement[];
  version?: number;
}

// Conflict error types
export interface ConflictErrorResponse {
  code: "VERSION_CONFLICT";
  message: string;
  expectedVersion: number;
  actualVersion: number;
}

export class VersionConflictError extends Error {
  expectedVersion: number;
  actualVersion: number;

  constructor(conflict: ConflictErrorResponse) {
    super(conflict.message);
    this.name = "VersionConflictError";
    this.expectedVersion = conflict.expectedVersion;
    this.actualVersion = conflict.actualVersion;
  }
}

// Available LLM models
export type LlmModel = "claude-sonnet" | "claude-opus" | "groq";

// Chat API types
export interface ChatMessage {
  role: "user" | "assistant";
  content: string;
}

export interface ChatRequest {
  message: string;
  model?: LlmModel;
  history?: ChatMessage[];
  focusedElementIndex?: number;
}

export interface ToolCallInfo {
  name: string;
  result: {
    success: boolean;
    message: string;
  };
}

// User question types for interactive LLM tool
export interface UserQuestion {
  id: string;
  question: string;
  options: string[];
  allowMultiple: boolean;
  allowCustom: boolean;
}

export interface UserAnswer {
  id: string;
  answers: string[];
}

export interface ChatResponse {
  response: string;
  toolCalls: ToolCallInfo[];
  updatedBody: BodyElement[];
  updatedSummary: string | null;
  pendingQuestions?: UserQuestion[];
}

// File API functions
export async function listFiles(): Promise<FileListResponse> {
  const res = await authFetch(`${API_BASE}/api/v1/files`);
  if (!res.ok) {
    throw new Error(`Failed to list files: ${res.statusText}`);
  }
  return res.json();
}

export async function getFile(id: string): Promise<FileDetail> {
  const res = await authFetch(`${API_BASE}/api/v1/files/${id}`);
  if (!res.ok) {
    throw new Error(`Failed to get file: ${res.statusText}`);
  }
  return res.json();
}

export async function createFile(data: CreateFileRequest): Promise<FileDetail> {
  const res = await authFetch(`${API_BASE}/api/v1/files`, {
    method: "POST",
    body: JSON.stringify(data),
  });
  if (!res.ok) {
    throw new Error(`Failed to create file: ${res.statusText}`);
  }
  return res.json();
}

export async function updateFile(
  id: string,
  data: UpdateFileRequest
): Promise<FileDetail> {
  const res = await authFetch(`${API_BASE}/api/v1/files/${id}`, {
    method: "PUT",
    body: JSON.stringify(data),
  });

  if (res.status === 409) {
    const conflict = (await res.json()) as ConflictErrorResponse;
    throw new VersionConflictError(conflict);
  }

  if (!res.ok) {
    throw new Error(`Failed to update file: ${res.statusText}`);
  }
  return res.json();
}

export async function deleteFile(id: string): Promise<void> {
  const res = await authFetch(`${API_BASE}/api/v1/files/${id}`, {
    method: "DELETE",
  });
  if (!res.ok) {
    throw new Error(`Failed to delete file: ${res.statusText}`);
  }
}

// Chat API function
export async function chatWithFile(
  id: string,
  message: string,
  model?: LlmModel,
  history?: ChatMessage[],
  focusedElementIndex?: number
): Promise<ChatResponse> {
  const body: ChatRequest = { message };
  if (model) {
    body.model = model;
  }
  if (history && history.length > 0) {
    body.history = history;
  }
  if (focusedElementIndex !== undefined) {
    body.focusedElementIndex = focusedElementIndex;
  }
  const res = await authFetch(`${API_BASE}/api/v1/files/${id}/chat`, {
    method: "POST",
    body: JSON.stringify(body),
  });
  if (!res.ok) {
    const errorText = await res.text();
    throw new Error(`Chat failed: ${errorText || res.statusText}`);
  }
  return res.json();
}

// Version history types
export type VersionSource = "user" | "llm" | "system";

export interface FileVersion {
  version: number;
  name: string;
  description: string | null;
  summary: string | null;
  body: BodyElement[];
  source: VersionSource;
  createdAt: string;
  changeDescription?: string;
}

export interface FileVersionSummary {
  version: number;
  source: VersionSource;
  createdAt: string;
  changeDescription?: string;
}

export interface FileVersionListResponse {
  versions: FileVersionSummary[];
  total: number;
}

export interface RestoreVersionRequest {
  targetVersion: number;
}

// Version history API functions
export async function listFileVersions(fileId: string): Promise<FileVersionListResponse> {
  const res = await authFetch(`${API_BASE}/api/v1/files/${fileId}/versions`);
  if (!res.ok) {
    throw new Error(`Failed to list versions: ${res.statusText}`);
  }
  return res.json();
}

export async function getFileVersion(fileId: string, version: number): Promise<FileVersion> {
  const res = await authFetch(`${API_BASE}/api/v1/files/${fileId}/versions/${version}`);
  if (!res.ok) {
    throw new Error(`Failed to get version: ${res.statusText}`);
  }
  return res.json();
}

export async function restoreFileVersion(
  fileId: string,
  targetVersion: number,
  currentVersion: number
): Promise<FileDetail> {
  const res = await authFetch(`${API_BASE}/api/v1/files/${fileId}/versions/restore`, {
    method: "POST",
    body: JSON.stringify({ targetVersion, currentVersion }),
  });

  if (res.status === 409) {
    const conflict = (await res.json()) as ConflictErrorResponse;
    throw new VersionConflictError(conflict);
  }

  if (!res.ok) {
    throw new Error(`Failed to restore version: ${res.statusText}`);
  }
  return res.json();
}

/**
 * Sync a file from its linked repository file.
 * Triggers an async operation - the file will be updated when the daemon responds.
 * Returns 202 Accepted if the sync started successfully.
 */
export async function syncFileFromRepo(fileId: string): Promise<{ message: string; fileId: string }> {
  const res = await authFetch(`${API_BASE}/api/v1/files/${fileId}/sync-from-repo`, {
    method: "POST",
  });

  if (!res.ok) {
    const error = await res.json();
    throw new Error(error.message || `Failed to sync file: ${res.statusText}`);
  }
  return res.json();
}

// =============================================================================
// LLM Tool Definitions for Version History
// =============================================================================
// These types define the tools available to the LLM for version history access.
// The backend should implement handlers for these tools.

/**
 * Tool: read_version
 * Allows the LLM to read the content of a specific historical version.
 * This is read-only - it does not modify the document.
 */
export interface ReadVersionToolInput {
  version: number;
}

export interface ReadVersionToolOutput {
  success: boolean;
  version: number;
  body: BodyElement[];
  summary: string | null;
  source: VersionSource;
  createdAt: string;
  changeDescription?: string;
  message: string;
}

/**
 * Tool: list_versions
 * Allows the LLM to list all available versions of the document.
 */
export interface ListVersionsToolOutput {
  success: boolean;
  versions: FileVersionSummary[];
  currentVersion: number;
  message: string;
}

/**
 * Tool: restore_version
 * Allows the LLM to restore the document to a specific historical version.
 * This creates a new version with the content from the target version.
 */
export interface RestoreVersionToolInput {
  targetVersion: number;
  reason?: string;
}

export interface RestoreVersionToolOutput {
  success: boolean;
  previousVersion: number;
  newVersion: number;
  restoredFromVersion: number;
  message: string;
}

// LLM Tool type definitions for the backend
export type LlmVersionTool =
  | { name: "read_version"; input: ReadVersionToolInput }
  | { name: "list_versions"; input: Record<string, never> }
  | { name: "restore_version"; input: RestoreVersionToolInput };

export type LlmVersionToolResult =
  | { name: "read_version"; result: ReadVersionToolOutput }
  | { name: "list_versions"; result: ListVersionsToolOutput }
  | { name: "restore_version"; result: RestoreVersionToolOutput };

// =============================================================================
// Mesh/Task Types for Claude Code Orchestration
// =============================================================================

export type TaskStatus =
  | "pending"
  | "initializing"
  | "starting"
  | "running"
  | "paused"
  | "blocked"
  | "done"
  | "failed"
  | "merged";

export type MergeMode = "pr" | "auto" | "manual";

/** Action to perform when a task completes successfully */
export type CompletionAction = "none" | "branch" | "merge" | "pr";

export type DaemonStatus = "connected" | "disconnected" | "unhealthy";

export interface TaskSummary {
  id: string;
  /** Contract this task belongs to */
  contractId: string | null;
  /** Contract name (joined from contracts table) */
  contractName: string | null;
  /** Contract phase (joined from contracts table) */
  contractPhase: ContractPhase | null;
  /** Contract status (joined from contracts table) */
  contractStatus: ContractStatus | null;
  parentTaskId: string | null;
  depth: number;
  name: string;
  status: TaskStatus;
  priority: number;
  progressSummary: string | null;
  subtaskCount: number;
  /** Whether this is a supervisor task (contract orchestrator) */
  isSupervisor: boolean;
  version: number;
  createdAt: string;
  updatedAt: string;
}

export interface Task {
  id: string;
  ownerId: string;
  /** Contract this task belongs to */
  contractId: string | null;
  parentTaskId: string | null;
  depth: number;
  name: string;
  description: string | null;
  status: TaskStatus;
  priority: number;
  plan: string;

  // Daemon/container info
  daemonId: string | null;
  containerId: string | null;
  overlayPath: string | null;

  // Repository info
  repositoryUrl: string | null;
  baseBranch: string | null;
  targetBranch: string | null;

  // Merge settings
  mergeMode: MergeMode | null;
  prUrl: string | null;

  // Completion action settings
  /** Path to user's local repository for completion actions */
  targetRepoPath: string | null;
  /** Action on completion: "none", "branch", "merge", "pr" */
  completionAction: CompletionAction | null;

  // Progress tracking
  progressSummary: string | null;
  lastOutput: string | null;
  errorMessage: string | null;

  // Timestamps
  startedAt: string | null;
  completedAt: string | null;
  version: number;
  createdAt: string;
  updatedAt: string;

  // Supervisor flag
  isSupervisor: boolean;
}

export interface TaskWithSubtasks extends Task {
  subtasks: TaskSummary[];
}

export interface TaskListResponse {
  tasks: TaskSummary[];
  total: number;
}

export interface CreateTaskRequest {
  /** Contract this task belongs to (optional - can be standalone) */
  contractId?: string;
  name: string;
  description?: string;
  plan: string;
  parentTaskId?: string;
  priority?: number;
  repositoryUrl?: string;
  baseBranch?: string;
  targetBranch?: string;
  mergeMode?: MergeMode;
  /** Path to user's local repository for completion actions */
  targetRepoPath?: string;
  /** Action on completion: "none", "branch", "merge", "pr" */
  completionAction?: CompletionAction;
}

export interface UpdateTaskRequest {
  name?: string;
  description?: string;
  plan?: string;
  status?: TaskStatus;
  priority?: number;
  progressSummary?: string;
  lastOutput?: string;
  errorMessage?: string;
  mergeMode?: MergeMode;
  prUrl?: string;
  /** Path to user's local repository for completion actions */
  targetRepoPath?: string;
  /** Action on completion: "none", "branch", "merge", "pr" */
  completionAction?: CompletionAction;
  version?: number;
}

export interface TaskEvent {
  id: string;
  taskId: string;
  eventType: string;
  previousStatus: string | null;
  newStatus: string | null;
  eventData: Record<string, unknown> | null;
  createdAt: string;
}

export interface TaskEventListResponse {
  events: TaskEvent[];
  total: number;
}

export interface Daemon {
  id: string;
  ownerId: string;
  connectionId: string;
  hostname: string | null;
  machineId: string | null;
  maxConcurrentTasks: number;
  currentTaskCount: number;
  status: DaemonStatus;
  lastHeartbeatAt: string;
  connectedAt: string;
  disconnectedAt: string | null;
}

export interface DaemonListResponse {
  daemons: Daemon[];
  total: number;
}

// Mesh API functions
export async function listTasks(): Promise<TaskListResponse> {
  const res = await authFetch(`${API_BASE}/api/v1/mesh/tasks`);
  if (!res.ok) {
    throw new Error(`Failed to list tasks: ${res.statusText}`);
  }
  return res.json();
}

export async function getTask(id: string): Promise<TaskWithSubtasks> {
  const res = await authFetch(`${API_BASE}/api/v1/mesh/tasks/${id}`);
  if (!res.ok) {
    throw new Error(`Failed to get task: ${res.statusText}`);
  }
  return res.json();
}

export async function createTask(data: CreateTaskRequest): Promise<Task> {
  const res = await authFetch(`${API_BASE}/api/v1/mesh/tasks`, {
    method: "POST",
    body: JSON.stringify(data),
  });
  if (!res.ok) {
    throw new Error(`Failed to create task: ${res.statusText}`);
  }
  return res.json();
}

export async function updateTask(
  id: string,
  data: UpdateTaskRequest
): Promise<Task> {
  const res = await authFetch(`${API_BASE}/api/v1/mesh/tasks/${id}`, {
    method: "PUT",
    body: JSON.stringify(data),
  });

  if (res.status === 409) {
    const conflict = (await res.json()) as ConflictErrorResponse;
    throw new VersionConflictError(conflict);
  }

  if (!res.ok) {
    throw new Error(`Failed to update task: ${res.statusText}`);
  }
  return res.json();
}

export async function deleteTask(id: string): Promise<void> {
  const res = await authFetch(`${API_BASE}/api/v1/mesh/tasks/${id}`, {
    method: "DELETE",
  });
  if (!res.ok) {
    throw new Error(`Failed to delete task: ${res.statusText}`);
  }
}

export async function startTask(id: string): Promise<Task> {
  const res = await authFetch(`${API_BASE}/api/v1/mesh/tasks/${id}/start`, {
    method: "POST",
  });
  if (!res.ok) {
    const errorText = await res.text();
    throw new Error(`Failed to start task: ${errorText || res.statusText}`);
  }
  return res.json();
}

export async function stopTask(id: string): Promise<Task> {
  const res = await authFetch(`${API_BASE}/api/v1/mesh/tasks/${id}/stop`, {
    method: "POST",
  });
  if (!res.ok) {
    const errorText = await res.text();
    throw new Error(`Failed to stop task: ${errorText || res.statusText}`);
  }
  return res.json();
}

export interface SendMessageResponse {
  success: boolean;
  taskId: string;
  messageLength: number;
}

/**
 * Send a message to a running task's stdin.
 * This can be used to provide input to Claude Code when it's waiting for user input,
 * or to inject context/instructions into a running task.
 */
export async function sendTaskMessage(
  taskId: string,
  message: string
): Promise<SendMessageResponse> {
  const res = await authFetch(`${API_BASE}/api/v1/mesh/tasks/${taskId}/message`, {
    method: "POST",
    body: JSON.stringify({ message }),
  });
  if (!res.ok) {
    const errorText = await res.text();
    throw new Error(`Failed to send message: ${errorText || res.statusText}`);
  }
  return res.json();
}

export interface RetryCompletionResponse {
  success: boolean;
  taskId: string;
  action: string;
  targetRepoPath: string;
  message: string;
}

/**
 * Retry completion action for a completed task.
 * This allows retrying a completion action (push branch, merge, create PR)
 * after filling in the target_repo_path if it wasn't set when the task completed.
 */
export async function retryCompletionAction(
  taskId: string
): Promise<RetryCompletionResponse> {
  const res = await authFetch(`${API_BASE}/api/v1/mesh/tasks/${taskId}/retry-completion`, {
    method: "POST",
  });
  if (!res.ok) {
    const errorText = await res.text();
    throw new Error(`Failed to retry completion action: ${errorText || res.statusText}`);
  }
  return res.json();
}

/** A suggested directory from a connected daemon */
export interface DaemonDirectory {
  /** Path to the directory */
  path: string;
  /** Display label for the directory */
  label: string;
  /** Type of directory: "working", "makima", "worktrees" */
  directoryType: string;
  /** Daemon hostname this directory is from */
  hostname: string | null;
  /** Whether the directory already exists (for validation) */
  exists?: boolean;
}

export interface DaemonDirectoriesResponse {
  directories: DaemonDirectory[];
}

/**
 * Get suggested directories from connected daemons.
 * These can be used as target_repo_path suggestions for completion actions.
 */
export async function getDaemonDirectories(): Promise<DaemonDirectoriesResponse> {
  const res = await authFetch(`${API_BASE}/api/v1/mesh/daemons/directories`);
  if (!res.ok) {
    throw new Error(`Failed to get daemon directories: ${res.statusText}`);
  }
  return res.json();
}

/** Request to clone a worktree */
export interface CloneWorktreeRequest {
  targetDir: string;
}

/** Response from clone worktree */
export interface CloneWorktreeResponse {
  status: string;
  taskId: string;
  targetDir: string;
}

/**
 * Clone a task's worktree to a target directory.
 */
export async function cloneWorktree(
  taskId: string,
  targetDir: string
): Promise<CloneWorktreeResponse> {
  const res = await authFetch(`${API_BASE}/api/v1/mesh/tasks/${taskId}/clone`, {
    method: "POST",
    body: JSON.stringify({ targetDir }),
  });
  if (!res.ok) {
    const errorText = await res.text();
    throw new Error(`Failed to clone worktree: ${errorText || res.statusText}`);
  }
  return res.json();
}

/** Request to check if target exists */
export interface CheckTargetExistsRequest {
  targetDir: string;
}

/** Response from check target exists */
export interface CheckTargetExistsResponse {
  status: string;
  taskId: string;
  targetDir: string;
}

/**
 * Check if a target directory exists.
 */
export async function checkTargetExists(
  taskId: string,
  targetDir: string
): Promise<CheckTargetExistsResponse> {
  const res = await authFetch(`${API_BASE}/api/v1/mesh/tasks/${taskId}/check-target`, {
    method: "POST",
    body: JSON.stringify({ targetDir }),
  });
  if (!res.ok) {
    const errorText = await res.text();
    throw new Error(`Failed to check target: ${errorText || res.statusText}`);
  }
  return res.json();
}

// =============================================================================
// Task Recovery (Daemon Failover)
// =============================================================================

/** Request to reassign a task to a new daemon */
export interface ReassignTaskRequest {
  targetDaemonId?: string;
  includeContext?: boolean;
}

/** Response from reassigning a task */
export interface ReassignTaskResponse {
  task: Task;
  daemonId: string;
  oldTaskId: string;
  contextIncluded: boolean;
  contextEntries: number;
}

/**
 * Reassign a task to a new daemon after daemon disconnect.
 * Creates a new task with conversation context, deletes the old one.
 */
export async function reassignTask(
  taskId: string,
  options?: ReassignTaskRequest
): Promise<ReassignTaskResponse> {
  const res = await authFetch(`${API_BASE}/api/v1/mesh/tasks/${taskId}/reassign`, {
    method: "POST",
    body: JSON.stringify(options || {}),
  });
  if (!res.ok) {
    const errorText = await res.text();
    throw new Error(`Failed to reassign task: ${errorText || res.statusText}`);
  }
  return res.json();
}

/** Request to continue a task */
export interface ContinueTaskRequest {
  targetDaemonId?: string;
}

/** Response from continuing a task */
export interface ContinueTaskResponse {
  task: Task;
  daemonId: string;
  contextEntries: number;
}

/**
 * Continue a task after daemon disconnect by restarting it with conversation context.
 * Unlike reassign, this keeps the same task ID.
 */
export async function continueTask(
  taskId: string,
  options?: ContinueTaskRequest
): Promise<ContinueTaskResponse> {
  const res = await authFetch(`${API_BASE}/api/v1/mesh/tasks/${taskId}/continue`, {
    method: "POST",
    body: JSON.stringify(options || {}),
  });
  if (!res.ok) {
    const errorText = await res.text();
    throw new Error(`Failed to continue task: ${errorText || res.statusText}`);
  }
  return res.json();
}

export async function listSubtasks(taskId: string): Promise<TaskListResponse> {
  const res = await authFetch(`${API_BASE}/api/v1/mesh/tasks/${taskId}/subtasks`);
  if (!res.ok) {
    throw new Error(`Failed to list subtasks: ${res.statusText}`);
  }
  return res.json();
}

// =============================================================================
// Task Branching
// =============================================================================

/** Request to branch a task */
export interface BranchTaskRequest {
  /** Message to send to the new branched task */
  message: string;
  /** Optional name for the new task (defaults to "Branch of {original task name}") */
  name?: string;
  /** Whether to include conversation history from the source task */
  includeConversation?: boolean;
}

/** Response from branching a task */
export interface BranchTaskResponse {
  /** The newly created task */
  task: Task;
  /** Number of conversation messages copied to the new task */
  messageCount: number;
  /** ID of the daemon assigned to the new task (null if not yet assigned) */
  daemonId: string | null;
}

/**
 * Branch a task to create a new task with the same state.
 * Copies the worktree and optionally the conversation history.
 */
export async function branchTask(
  taskId: string,
  request: BranchTaskRequest
): Promise<BranchTaskResponse> {
  const res = await authFetch(`${API_BASE}/api/v1/mesh/tasks/${taskId}/branch`, {
    method: "POST",
    body: JSON.stringify(request),
  });
  if (!res.ok) {
    const errorText = await res.text();
    throw new Error(`Failed to branch task: ${errorText || res.statusText}`);
  }
  return res.json();
}

export async function listTaskEvents(
  taskId: string
): Promise<TaskEventListResponse> {
  const res = await authFetch(`${API_BASE}/api/v1/mesh/tasks/${taskId}/events`);
  if (!res.ok) {
    throw new Error(`Failed to list task events: ${res.statusText}`);
  }
  return res.json();
}

/** A single output entry from a Claude Code task */
export interface TaskOutputEntry {
  id: string;
  taskId: string;
  /** Message type: "assistant", "tool_use", "tool_result", "result", "system", "error", "raw" */
  messageType: string;
  /** Main text content */
  content: string;
  /** Tool name if tool_use message */
  toolName?: string;
  /** Tool input JSON if tool_use message */
  toolInput?: Record<string, unknown>;
  /** Whether tool result was an error */
  isError?: boolean;
  /** Cost in USD if result message */
  costUsd?: number;
  /** Duration in ms if result message */
  durationMs?: number;
  /** Timestamp when this output was recorded */
  createdAt: string;
}

/** Response from the task output endpoint */
export interface TaskOutputResponse {
  entries: TaskOutputEntry[];
  total: number;
  taskId: string;
}

/**
 * Get task output history.
 * Retrieves all recorded output from a task's Claude Code process.
 * Use this to fetch missed output when subscribing late or reconnecting.
 */
export async function getTaskOutput(
  taskId: string
): Promise<TaskOutputResponse> {
  const res = await authFetch(`${API_BASE}/api/v1/mesh/tasks/${taskId}/output`);
  if (!res.ok) {
    throw new Error(`Failed to get task output: ${res.statusText}`);
  }
  return res.json();
}

export async function listDaemons(): Promise<DaemonListResponse> {
  const res = await authFetch(`${API_BASE}/api/v1/mesh/daemons`);
  if (!res.ok) {
    throw new Error(`Failed to list daemons: ${res.statusText}`);
  }
  return res.json();
}

export async function getDaemon(id: string): Promise<Daemon> {
  const res = await authFetch(`${API_BASE}/api/v1/mesh/daemons/${id}`);
  if (!res.ok) {
    throw new Error(`Failed to get daemon: ${res.statusText}`);
  }
  return res.json();
}

// =============================================================================
// Mesh Chat Types for Task Orchestration
// =============================================================================

export interface MeshChatMessage {
  role: "user" | "assistant";
  content: string;
}

export interface MeshChatRequest {
  message: string;
  model?: LlmModel;
  history?: MeshChatMessage[];
}

export interface MeshToolCallInfo {
  name: string;
  result: {
    success: boolean;
    message: string;
  };
}

export interface MeshChatResponse {
  response: string;
  toolCalls: MeshToolCallInfo[];
  pendingQuestions?: UserQuestion[];
}

// Mesh Chat API functions

// Top-level mesh chat (no specific task context)
export async function chatWithMesh(
  message: string,
  model?: LlmModel,
  history?: MeshChatMessage[]
): Promise<MeshChatResponse> {
  const body: MeshChatRequest = { message };
  if (model) {
    body.model = model;
  }
  if (history && history.length > 0) {
    body.history = history;
  }
  const res = await authFetch(`${API_BASE}/api/v1/mesh/chat`, {
    method: "POST",
    body: JSON.stringify(body),
  });
  if (!res.ok) {
    const errorText = await res.text();
    throw new Error(`Mesh chat failed: ${errorText || res.statusText}`);
  }
  return res.json();
}

// Task-scoped mesh chat
export async function chatWithTask(
  taskId: string,
  message: string,
  model?: LlmModel,
  history?: MeshChatMessage[]
): Promise<MeshChatResponse> {
  const body: MeshChatRequest = { message };
  if (model) {
    body.model = model;
  }
  if (history && history.length > 0) {
    body.history = history;
  }
  const res = await authFetch(`${API_BASE}/api/v1/mesh/tasks/${taskId}/chat`, {
    method: "POST",
    body: JSON.stringify(body),
  });
  if (!res.ok) {
    const errorText = await res.text();
    throw new Error(`Mesh chat failed: ${errorText || res.statusText}`);
  }
  return res.json();
}

// =============================================================================
// Mesh Chat History Types
// =============================================================================

export type MeshChatContextType = "mesh" | "task" | "subtask";

export interface MeshChatContext {
  type: MeshChatContextType;
  taskId?: string;
  parentTaskId?: string;
}

export interface MeshChatMessageRecord {
  id: string;
  conversationId: string;
  role: "user" | "assistant" | "error";
  content: string;
  contextType: MeshChatContextType;
  contextTaskId: string | null;
  toolCalls: MeshToolCallInfo[] | null;
  pendingQuestions: UserQuestion[] | null;
  createdAt: string;
}

export interface MeshChatHistoryResponse {
  conversationId: string;
  messages: MeshChatMessageRecord[];
}

export interface MeshChatWithContextRequest {
  message: string;
  model?: LlmModel;
  contextType?: MeshChatContextType;
  contextTaskId?: string;
}

// =============================================================================
// Mesh Chat History API Functions
// =============================================================================

/**
 * Get the current chat history from the database
 */
export async function getMeshChatHistory(): Promise<MeshChatHistoryResponse> {
  const res = await authFetch(`${API_BASE}/api/v1/mesh/chat/history`);
  if (!res.ok) {
    throw new Error(`Failed to get chat history: ${res.statusText}`);
  }
  return res.json();
}

/**
 * Clear chat history (archives current conversation, starts new one)
 */
export async function clearMeshChatHistory(): Promise<{ success: boolean; conversationId: string }> {
  const res = await authFetch(`${API_BASE}/api/v1/mesh/chat/history`, {
    method: "DELETE",
  });
  if (!res.ok) {
    throw new Error(`Failed to clear chat history: ${res.statusText}`);
  }
  return res.json();
}

/**
 * Chat with mesh using context (new approach with DB history)
 */
export async function chatWithMeshContext(
  message: string,
  context: MeshChatContext,
  model?: LlmModel
): Promise<MeshChatResponse> {
  const body: MeshChatWithContextRequest = {
    message,
    contextType: context.type,
  };

  if (model) {
    body.model = model;
  }

  // Set contextTaskId based on context type
  if (context.type === "task" && context.taskId) {
    body.contextTaskId = context.taskId;
  } else if (context.type === "subtask" && context.taskId) {
    body.contextTaskId = context.taskId;
  }

  // Use top-level endpoint (it now loads history from DB)
  const res = await authFetch(`${API_BASE}/api/v1/mesh/chat`, {
    method: "POST",
    body: JSON.stringify(body),
  });

  if (!res.ok) {
    const errorText = await res.text();
    throw new Error(`Mesh chat failed: ${errorText || res.statusText}`);
  }
  return res.json();
}

// =============================================================================
// API Key Management
// =============================================================================

export interface ApiKeyInfo {
  id: string;
  prefix: string;
  name: string | null;
  lastUsedAt: string | null;
  createdAt: string;
}

export interface CreateApiKeyResponse {
  id: string;
  key: string;
  prefix: string;
  name: string | null;
  createdAt: string;
}

export interface RefreshApiKeyResponse {
  id: string;
  key: string;
  prefix: string;
  name: string | null;
  createdAt: string;
  previousKeyRevoked: boolean;
}

export interface RevokeApiKeyResponse {
  message: string;
  revokedKeyPrefix: string;
}

/**
 * Get information about the current active API key.
 */
export async function getApiKey(): Promise<ApiKeyInfo | null> {
  const res = await authFetch(`${API_BASE}/api/v1/auth/api-keys`);
  if (res.status === 404) {
    return null;
  }
  if (!res.ok) {
    throw new Error(`Failed to get API key: ${res.statusText}`);
  }
  return res.json();
}

/**
 * Create a new API key.
 */
export async function createApiKey(name?: string): Promise<CreateApiKeyResponse> {
  const res = await authFetch(`${API_BASE}/api/v1/auth/api-keys`, {
    method: "POST",
    body: JSON.stringify({ name }),
  });
  if (!res.ok) {
    const errorText = await res.text();
    throw new Error(`Failed to create API key: ${errorText || res.statusText}`);
  }
  return res.json();
}

/**
 * Refresh (rotate) the current API key.
 */
export async function refreshApiKey(name?: string): Promise<RefreshApiKeyResponse> {
  const res = await authFetch(`${API_BASE}/api/v1/auth/api-keys/refresh`, {
    method: "POST",
    body: JSON.stringify({ name }),
  });
  if (!res.ok) {
    const errorText = await res.text();
    throw new Error(`Failed to refresh API key: ${errorText || res.statusText}`);
  }
  return res.json();
}

/**
 * Revoke the current API key.
 */
export async function revokeApiKey(): Promise<RevokeApiKeyResponse> {
  const res = await authFetch(`${API_BASE}/api/v1/auth/api-keys`, {
    method: "DELETE",
  });
  if (!res.ok) {
    const errorText = await res.text();
    throw new Error(`Failed to revoke API key: ${errorText || res.statusText}`);
  }
  return res.json();
}

// =============================================================================
// User Account Management
// =============================================================================

export interface ChangePasswordRequest {
  currentPassword: string;
  newPassword: string;
}

export interface ChangePasswordResponse {
  success: boolean;
  message: string;
}

export interface ChangeEmailRequest {
  password: string;
  newEmail: string;
}

export interface ChangeEmailResponse {
  success: boolean;
  message: string;
  verificationSent: boolean;
}

export interface DeleteAccountRequest {
  password: string;
  confirmation: string;
}

export interface DeleteAccountResponse {
  success: boolean;
  message: string;
}

/**
 * Change the current user's password.
 * Requires current password verification.
 */
export async function changePassword(
  currentPassword: string,
  newPassword: string
): Promise<ChangePasswordResponse> {
  const res = await authFetch(`${API_BASE}/api/v1/users/me/password`, {
    method: "PUT",
    body: JSON.stringify({ currentPassword, newPassword }),
  });
  if (!res.ok) {
    const errorData = await res.json().catch(() => null);
    const errorMessage = errorData?.message || res.statusText;
    throw new Error(errorMessage);
  }
  return res.json();
}

/**
 * Change the current user's email address.
 * Requires password verification.
 */
export async function changeEmail(
  password: string,
  newEmail: string
): Promise<ChangeEmailResponse> {
  const res = await authFetch(`${API_BASE}/api/v1/users/me/email`, {
    method: "PUT",
    body: JSON.stringify({ password, newEmail }),
  });
  if (!res.ok) {
    const errorData = await res.json().catch(() => null);
    const errorMessage = errorData?.message || res.statusText;
    throw new Error(errorMessage);
  }
  return res.json();
}

/**
 * Delete the current user's account.
 * Requires password verification and email confirmation.
 */
export async function deleteAccount(
  password: string,
  confirmation: string
): Promise<DeleteAccountResponse> {
  const res = await authFetch(`${API_BASE}/api/v1/users/me`, {
    method: "DELETE",
    body: JSON.stringify({ password, confirmation }),
  });
  if (!res.ok) {
    const errorData = await res.json().catch(() => null);
    const errorMessage = errorData?.message || res.statusText;
    throw new Error(errorMessage);
  }
  return res.json();
}

// =============================================================================
// Contract Types for Workflow Management
// =============================================================================

/** Contract type determines the workflow and required documents */
export type ContractType = "simple" | "specification";
export type ContractPhase = "research" | "specify" | "plan" | "execute" | "review";
export type ContractStatus = "active" | "completed" | "archived";
export type RepositorySourceType = "remote" | "local" | "managed";
export type RepositoryStatus = "ready" | "pending" | "creating" | "failed";

/** Get valid phases for a contract type */
export function getValidPhases(contractType: ContractType): ContractPhase[] {
  if (contractType === "simple") {
    return ["plan", "execute"];
  }
  return ["research", "specify", "plan", "execute", "review"];
}

/** Get default initial phase for a contract type */
export function getDefaultPhase(contractType: ContractType): ContractPhase {
  if (contractType === "simple") return "plan";
  return "research";
}

export interface ContractRepository {
  id: string;
  contractId: string;
  name: string;
  repositoryUrl: string | null;
  localPath: string | null;
  sourceType: RepositorySourceType;
  status: RepositoryStatus;
  isPrimary: boolean;
  createdAt: string;
  updatedAt: string;
}

export interface ContractSummary {
  id: string;
  name: string;
  description: string | null;
  /** Contract type: "simple" or "specification" */
  contractType: ContractType;
  phase: ContractPhase;
  status: ContractStatus;
  /** Supervisor task ID for contract orchestration */
  supervisorTaskId: string | null;
  fileCount: number;
  taskCount: number;
  repositoryCount: number;
  version: number;
  createdAt: string;
}

export interface Contract {
  id: string;
  ownerId: string;
  name: string;
  description: string | null;
  /** Contract type: "simple" or "specification" */
  contractType: ContractType;
  phase: ContractPhase;
  status: ContractStatus;
  /** Supervisor task ID for contract orchestration */
  supervisorTaskId: string | null;
  /** Whether tasks for this contract should run in autonomous loop mode */
  autonomousLoop: boolean;
  /** Whether to wait for user confirmation before progressing to the next phase */
  phaseGuard: boolean;
  version: number;
  createdAt: string;
  updatedAt: string;
}

export interface ContractWithRelations extends Contract {
  repositories: ContractRepository[];
  files: FileSummary[];
  tasks: TaskSummary[];
}

export interface ContractEvent {
  id: string;
  contractId: string;
  eventType: string;
  previousPhase: string | null;
  newPhase: string | null;
  eventData: Record<string, unknown> | null;
  createdAt: string;
}

export interface ContractListResponse {
  contracts: ContractSummary[];
  total: number;
}

export interface CreateContractRequest {
  name: string;
  description?: string;
  /** Contract type: "simple" (default) or "specification" */
  contractType?: ContractType;
  /** Initial phase to start in (defaults based on contract type) */
  initialPhase?: ContractPhase;
}

export interface UpdateContractRequest {
  name?: string;
  description?: string;
  phase?: ContractPhase;
  status?: ContractStatus;
  /** Enable or disable autonomous loop mode */
  autonomousLoop?: boolean;
  /** Enable or disable phase guard mode */
  phaseGuard?: boolean;
  version?: number;
}

export interface AddRemoteRepositoryRequest {
  name: string;
  repositoryUrl: string;
  isPrimary?: boolean;
}

export interface AddLocalRepositoryRequest {
  name: string;
  localPath: string;
  isPrimary?: boolean;
}

export interface CreateManagedRepositoryRequest {
  name: string;
  isPrimary?: boolean;
}

export interface ChangePhaseRequest {
  phase: ContractPhase;
}

// =============================================================================
// Contract API Functions
// =============================================================================

/**
 * List all contracts.
 */
export async function listContracts(): Promise<ContractListResponse> {
  const res = await authFetch(`${API_BASE}/api/v1/contracts`);
  if (!res.ok) {
    throw new Error(`Failed to list contracts: ${res.statusText}`);
  }
  return res.json();
}

/**
 * Get a contract with all its relations.
 */
export async function getContract(id: string): Promise<ContractWithRelations> {
  const res = await authFetch(`${API_BASE}/api/v1/contracts/${id}`);
  if (!res.ok) {
    throw new Error(`Failed to get contract: ${res.statusText}`);
  }
  return res.json();
}

/**
 * Create a new contract.
 */
export async function createContract(
  data: CreateContractRequest
): Promise<ContractSummary> {
  const res = await authFetch(`${API_BASE}/api/v1/contracts`, {
    method: "POST",
    body: JSON.stringify(data),
  });
  if (!res.ok) {
    throw new Error(`Failed to create contract: ${res.statusText}`);
  }
  return res.json();
}

/**
 * Update a contract.
 */
export async function updateContract(
  id: string,
  data: UpdateContractRequest
): Promise<ContractSummary> {
  const res = await authFetch(`${API_BASE}/api/v1/contracts/${id}`, {
    method: "PUT",
    body: JSON.stringify(data),
  });

  if (res.status === 409) {
    const conflict = (await res.json()) as ConflictErrorResponse;
    throw new VersionConflictError(conflict);
  }

  if (!res.ok) {
    throw new Error(`Failed to update contract: ${res.statusText}`);
  }
  return res.json();
}

/**
 * Delete a contract.
 */
export async function deleteContract(id: string): Promise<void> {
  const res = await authFetch(`${API_BASE}/api/v1/contracts/${id}`, {
    method: "DELETE",
  });
  if (!res.ok) {
    throw new Error(`Failed to delete contract: ${res.statusText}`);
  }
}

/**
 * Change contract phase.
 * @param confirmed - When true, confirms the phase change even if phase_guard is enabled.
 *                    Defaults to true for explicit user actions like drag-and-drop.
 */
export async function changeContractPhase(
  id: string,
  phase: ContractPhase,
  confirmed: boolean = true
): Promise<ContractSummary> {
  const res = await authFetch(`${API_BASE}/api/v1/contracts/${id}/phase`, {
    method: "POST",
    body: JSON.stringify({ phase, confirmed }),
  });
  if (!res.ok) {
    throw new Error(`Failed to change phase: ${res.statusText}`);
  }
  return res.json();
}

/**
 * Get contract event history.
 */
export async function getContractEvents(
  id: string
): Promise<ContractEvent[]> {
  const res = await authFetch(`${API_BASE}/api/v1/contracts/${id}/events`);
  if (!res.ok) {
    throw new Error(`Failed to get events: ${res.statusText}`);
  }
  return res.json();
}

// =============================================================================
// Contract Repository Management
// =============================================================================

/**
 * Add a remote repository to a contract.
 */
export async function addRemoteRepository(
  contractId: string,
  data: AddRemoteRepositoryRequest
): Promise<ContractRepository> {
  const res = await authFetch(
    `${API_BASE}/api/v1/contracts/${contractId}/repositories/remote`,
    {
      method: "POST",
      body: JSON.stringify(data),
    }
  );
  if (!res.ok) {
    throw new Error(`Failed to add remote repository: ${res.statusText}`);
  }
  return res.json();
}

/**
 * Add a local repository to a contract.
 */
export async function addLocalRepository(
  contractId: string,
  data: AddLocalRepositoryRequest
): Promise<ContractRepository> {
  const res = await authFetch(
    `${API_BASE}/api/v1/contracts/${contractId}/repositories/local`,
    {
      method: "POST",
      body: JSON.stringify(data),
    }
  );
  if (!res.ok) {
    throw new Error(`Failed to add local repository: ${res.statusText}`);
  }
  return res.json();
}

/**
 * Create a managed repository (daemon will create it).
 */
export async function createManagedRepository(
  contractId: string,
  data: CreateManagedRepositoryRequest
): Promise<ContractRepository> {
  const res = await authFetch(
    `${API_BASE}/api/v1/contracts/${contractId}/repositories/managed`,
    {
      method: "POST",
      body: JSON.stringify(data),
    }
  );
  if (!res.ok) {
    throw new Error(`Failed to create managed repository: ${res.statusText}`);
  }
  return res.json();
}

/**
 * Delete a repository from a contract.
 */
export async function deleteContractRepository(
  contractId: string,
  repoId: string
): Promise<void> {
  const res = await authFetch(
    `${API_BASE}/api/v1/contracts/${contractId}/repositories/${repoId}`,
    {
      method: "DELETE",
    }
  );
  if (!res.ok) {
    throw new Error(`Failed to delete repository: ${res.statusText}`);
  }
}

/**
 * Set a repository as primary.
 */
export async function setRepositoryPrimary(
  contractId: string,
  repoId: string
): Promise<void> {
  const res = await authFetch(
    `${API_BASE}/api/v1/contracts/${contractId}/repositories/${repoId}/primary`,
    {
      method: "PUT",
    }
  );
  if (!res.ok) {
    throw new Error(`Failed to set repository as primary: ${res.statusText}`);
  }
}

// =============================================================================
// Contract Task Association
// =============================================================================

/**
 * Add a task to a contract.
 */
export async function addTaskToContract(
  contractId: string,
  taskId: string
): Promise<void> {
  const res = await authFetch(
    `${API_BASE}/api/v1/contracts/${contractId}/tasks/${taskId}`,
    {
      method: "POST",
    }
  );
  if (!res.ok) {
    throw new Error(`Failed to add task to contract: ${res.statusText}`);
  }
}

/**
 * Remove a task from a contract.
 */
export async function removeTaskFromContract(
  contractId: string,
  taskId: string
): Promise<void> {
  const res = await authFetch(
    `${API_BASE}/api/v1/contracts/${contractId}/tasks/${taskId}`,
    {
      method: "DELETE",
    }
  );
  if (!res.ok) {
    throw new Error(`Failed to remove task from contract: ${res.statusText}`);
  }
}

// =============================================================================
// Contract Chat Types and API
// =============================================================================

export interface ContractChatRequest {
  message: string;
  model?: LlmModel;
  history?: ChatMessage[];
}

export interface ContractToolCallInfo {
  name: string;
  result: {
    success: boolean;
    message: string;
  };
}

export interface ContractChatResponse {
  response: string;
  toolCalls: ContractToolCallInfo[];
  pendingQuestions?: UserQuestion[];
}

/**
 * Chat with a contract using LLM-powered management tools.
 */
export async function chatWithContract(
  contractId: string,
  message: string,
  model?: LlmModel,
  history?: ChatMessage[]
): Promise<ContractChatResponse> {
  const body: ContractChatRequest = { message };
  if (model) {
    body.model = model;
  }
  if (history && history.length > 0) {
    body.history = history;
  }
  const res = await authFetch(`${API_BASE}/api/v1/contracts/${contractId}/chat`, {
    method: "POST",
    body: JSON.stringify(body),
  });
  if (!res.ok) {
    const errorText = await res.text();
    throw new Error(`Contract chat failed: ${errorText || res.statusText}`);
  }
  return res.json();
}

// Contract chat history types
export interface ContractChatMessage {
  id: string;
  conversationId: string;
  role: "user" | "assistant" | "error";
  content: string;
  toolCalls?: unknown;
  pendingQuestions?: unknown;
  createdAt: string;
}

export interface ContractChatHistoryResponse {
  contractId: string;
  conversationId: string;
  messages: ContractChatMessage[];
}

/** Get contract chat history */
export async function getContractChatHistory(
  contractId: string
): Promise<ContractChatHistoryResponse> {
  const res = await authFetch(
    `${API_BASE}/api/v1/contracts/${contractId}/chat/history`
  );
  if (!res.ok) {
    throw new Error(`Failed to fetch contract chat history: ${res.statusText}`);
  }
  return res.json();
}

/** Clear contract chat history (starts a new conversation) */
export async function clearContractChatHistory(
  contractId: string
): Promise<void> {
  const res = await authFetch(
    `${API_BASE}/api/v1/contracts/${contractId}/chat/history`,
    { method: "DELETE" }
  );
  if (!res.ok) {
    throw new Error(`Failed to clear contract chat history: ${res.statusText}`);
  }
}

// =============================================================================
// Template Types and API
// =============================================================================

export interface TemplateSummary {
  id: string;
  name: string;
  phase: ContractPhase;
  description: string;
  elementCount: number;
}

export interface FileTemplate {
  id: string;
  name: string;
  phase: ContractPhase;
  description: string;
  suggestedBody: BodyElement[];
}

export interface ListTemplatesResponse {
  templates: TemplateSummary[];
}

export async function listTemplates(
  phase?: ContractPhase
): Promise<ListTemplatesResponse> {
  const params = phase ? `?phase=${phase}` : "";
  const res = await authFetch(`${API_BASE}/api/v1/templates${params}`);
  if (!res.ok) {
    throw new Error(`Failed to list templates: ${res.statusText}`);
  }
  return res.json();
}

export async function getTemplate(id: string): Promise<FileTemplate> {
  const res = await authFetch(`${API_BASE}/api/v1/templates/${id}`);
  if (!res.ok) {
    throw new Error(`Failed to get template: ${res.statusText}`);
  }
  return res.json();
}

// =============================================================================
// Supervisor Question Types and Functions
// =============================================================================

export interface PendingQuestion {
  questionId: string;
  taskId: string;
  contractId: string;
  question: string;
  choices: string[];
  context: string | null;
  createdAt: string;
  /** Whether multiple choices can be selected */
  multiSelect?: boolean;
  /** Question type - "general" for regular questions, "phase_confirmation" for phase transitions, "contract_complete" for contract completion */
  questionType?: "general" | "phase_confirmation" | "contract_complete";
  /** Phase confirmation specific data (when questionType is "phase_confirmation") */
  phaseConfirmation?: {
    currentPhase: ContractPhase;
    nextPhase: ContractPhase;
    contractName?: string;
    summary?: string;
    deliverables?: Array<{
      name: string;
      completed: boolean;
    }>;
  };
}

export interface AnswerQuestionRequest {
  response: string;
}

export interface AnswerQuestionResponse {
  success: boolean;
}

/**
 * Get all pending supervisor questions for the current user.
 */
export async function listPendingQuestions(): Promise<PendingQuestion[]> {
  const res = await authFetch(`${API_BASE}/api/v1/mesh/questions`);
  if (!res.ok) {
    throw new Error(`Failed to list questions: ${res.statusText}`);
  }
  return res.json();
}

/**
 * Answer a pending supervisor question.
 */
export async function answerQuestion(
  questionId: string,
  response: string
): Promise<AnswerQuestionResponse> {
  const res = await authFetch(
    `${API_BASE}/api/v1/mesh/questions/${questionId}/answer`,
    {
      method: "POST",
      body: JSON.stringify({ response }),
    }
  );
  if (!res.ok) {
    throw new Error(`Failed to answer question: ${res.statusText}`);
  }
  return res.json();
}

// =============================================================================
// Repository History Types and API
// =============================================================================

export interface RepositoryHistoryEntry {
  id: string;
  ownerId: string;
  name: string;
  repositoryUrl: string | null;
  localPath: string | null;
  sourceType: "remote" | "local";
  useCount: number;
  lastUsedAt: string;
  createdAt: string;
}

export interface RepositoryHistoryListResponse {
  entries: RepositoryHistoryEntry[];
  total: number;
}

/**
 * List all repository history entries.
 * Returns entries ordered by use_count DESC, last_used_at DESC.
 */
export async function listRepositoryHistory(): Promise<RepositoryHistoryListResponse> {
  const res = await authFetch(`${API_BASE}/api/v1/settings/repository-history`);
  if (!res.ok) {
    throw new Error(`Failed to list repository history: ${res.statusText}`);
  }
  return res.json();
}

/**
 * Get repository suggestions based on history.
 * Optionally filter by source type and search query.
 */
export async function getRepositorySuggestions(
  sourceType?: "remote" | "local",
  query?: string,
  limit?: number
): Promise<RepositoryHistoryListResponse> {
  const params = new URLSearchParams();
  if (sourceType) params.append("source_type", sourceType);
  if (query) params.append("query", query);
  if (limit) params.append("limit", limit.toString());

  const queryString = params.toString();
  const url = `${API_BASE}/api/v1/settings/repository-history/suggestions${
    queryString ? `?${queryString}` : ""
  }`;

  const res = await authFetch(url);
  if (!res.ok) {
    throw new Error(`Failed to get repository suggestions: ${res.statusText}`);
  }
  return res.json();
}

/**
 * Delete a repository history entry.
 */
export async function deleteRepositoryHistory(id: string): Promise<void> {
  const res = await authFetch(
    `${API_BASE}/api/v1/settings/repository-history/${id}`,
    {
      method: "DELETE",
    }
  );
  if (!res.ok) {
    throw new Error(`Failed to delete repository history: ${res.statusText}`);
  }
}

// =============================================================================
// History Types
// =============================================================================

/** History event from the timeline */
export interface HistoryEvent {
  id: string;
  ownerId: string;
  contractId: string | null;
  taskId: string | null;
  eventType: string;
  eventSubtype: string | null;
  phase: string | null;
  eventData: Record<string, unknown>;
  createdAt: string;
}

/** Response for contract history endpoint */
export interface ContractHistoryResponse {
  contractId: string;
  entries: HistoryEvent[];
  totalCount: number;
  cursor: string | null;
}

/** Tool call info in conversation messages */
export interface ToolCallInfo {
  id: string;
  name: string;
  input: Record<string, unknown>;
}

/** Conversation message with optional tool calls */
export interface ConversationMessage {
  id: string;
  role: string;
  content: string;
  timestamp: string;
  toolCalls?: ToolCallInfo[];
  toolName?: string;
  toolInput?: Record<string, unknown>;
  toolResult?: string;
  isError?: boolean;
  tokenCount?: number;
  costUsd?: number;
}

/** Reference to a spawned task in supervisor conversation */
export interface TaskReference {
  taskId: string;
  taskName: string;
  status: string;
  createdAt: string;
  completedAt: string | null;
}

/** Response for supervisor conversation endpoint */
export interface SupervisorConversationResponse {
  contractId: string;
  supervisorTaskId: string;
  phase: string;
  lastActivity: string;
  pendingTaskIds: string[];
  messages: ConversationMessage[];
  spawnedTasks: TaskReference[];
}

/** Response for task conversation endpoint */
export interface TaskConversationResponse {
  taskId: string;
  taskName: string;
  status: string;
  messages: ConversationMessage[];
  totalTokens: number | null;
  totalCost: number | null;
}

/** Query filters for timeline endpoint */
export interface TimelineQueryFilters {
  contractId?: string;
  taskId?: string;
  includeSubtasks?: boolean;
  from?: string;
  to?: string;
  limit?: number;
}

/** Query filters for contract history endpoint */
export interface HistoryQueryFilters {
  phase?: string;
  eventTypes?: string;
  from?: string;
  to?: string;
  limit?: number;
}

/** Task checkpoint */
export interface TaskCheckpoint {
  id: string;
  taskId: string;
  checkpointNumber: number;
  commitSha: string;
  branchName: string;
  message: string;
  filesChanged: Array<{ path: string; action: string }>;
  linesAdded: number;
  linesRemoved: number;
  createdAt: string;
}

// =============================================================================
// History API Functions
// =============================================================================

/**
 * Get contract history timeline.
 */
export async function getContractHistory(
  contractId: string,
  filters?: HistoryQueryFilters
): Promise<ContractHistoryResponse> {
  const params = new URLSearchParams();
  if (filters?.phase) params.append("phase", filters.phase);
  if (filters?.eventTypes) params.append("event_types", filters.eventTypes);
  if (filters?.from) params.append("from", filters.from);
  if (filters?.to) params.append("to", filters.to);
  if (filters?.limit) params.append("limit", filters.limit.toString());

  const query = params.toString();
  const url = `${API_BASE}/api/v1/contracts/${contractId}/history${query ? `?${query}` : ""}`;

  const res = await authFetch(url);
  if (!res.ok) {
    throw new Error(`Failed to get contract history: ${res.statusText}`);
  }
  return res.json();
}

/**
 * Get supervisor conversation history.
 */
export async function getSupervisorConversation(
  contractId: string
): Promise<SupervisorConversationResponse> {
  const res = await authFetch(
    `${API_BASE}/api/v1/contracts/${contractId}/supervisor/conversation`
  );
  if (!res.ok) {
    throw new Error(`Failed to get supervisor conversation: ${res.statusText}`);
  }
  return res.json();
}

/**
 * Get task conversation history.
 */
export async function getTaskConversation(
  taskId: string,
  options?: { includeToolCalls?: boolean; includeToolResults?: boolean; limit?: number }
): Promise<TaskConversationResponse> {
  const params = new URLSearchParams();
  if (options?.includeToolCalls !== undefined)
    params.append("include_tool_calls", options.includeToolCalls.toString());
  if (options?.includeToolResults !== undefined)
    params.append("include_tool_results", options.includeToolResults.toString());
  if (options?.limit) params.append("limit", options.limit.toString());

  const query = params.toString();
  const url = `${API_BASE}/api/v1/mesh/tasks/${taskId}/conversation${query ? `?${query}` : ""}`;

  const res = await authFetch(url);
  if (!res.ok) {
    throw new Error(`Failed to get task conversation: ${res.statusText}`);
  }
  return res.json();
}

/**
 * Get unified timeline for current user.
 */
export async function getTimeline(
  filters?: TimelineQueryFilters
): Promise<ContractHistoryResponse> {
  const params = new URLSearchParams();
  if (filters?.contractId) params.append("contract_id", filters.contractId);
  if (filters?.taskId) params.append("task_id", filters.taskId);
  if (filters?.includeSubtasks !== undefined)
    params.append("include_subtasks", filters.includeSubtasks.toString());
  if (filters?.from) params.append("from", filters.from);
  if (filters?.to) params.append("to", filters.to);
  if (filters?.limit) params.append("limit", filters.limit.toString());

  const query = params.toString();
  const url = `${API_BASE}/api/v1/timeline${query ? `?${query}` : ""}`;

  const res = await authFetch(url);
  if (!res.ok) {
    throw new Error(`Failed to get timeline: ${res.statusText}`);
  }
  return res.json();
}

/**
 * Get task checkpoints.
 */
export async function getTaskCheckpoints(taskId: string): Promise<TaskCheckpoint[]> {
  const res = await authFetch(`${API_BASE}/api/v1/mesh/tasks/${taskId}/checkpoints`);
  if (!res.ok) {
    throw new Error(`Failed to get task checkpoints: ${res.statusText}`);
  }
  return res.json();
}

// =============================================================================
// Resume/Rewind/Fork Types
// =============================================================================

/** Request to rewind a task to a checkpoint */
export interface RewindTaskRequest {
  checkpointId?: string;
  checkpointSha?: string;
  preserveMode: "discard" | "create_branch" | "stash";
  branchName?: string;
}

/** Response from task rewind */
export interface RewindTaskResponse {
  taskId: string;
  rewindedTo: {
    checkpointNumber: number;
    sha: string;
    message: string;
  };
  preservedAs?: {
    stateType: string;
    reference: string;
  };
}

/** Request to fork a task from a checkpoint */
export interface ForkTaskRequest {
  forkFromType: "checkpoint" | "timestamp" | "message_id";
  forkFromValue: string;
  newTaskName: string;
  newTaskPlan: string;
  includeConversation?: boolean;
  createBranch?: boolean;
  branchName?: string;
}

/** Response from task fork */
export interface ForkTaskResponse {
  newTaskId: string;
  sourceTaskId: string;
  forkPoint: {
    forkType: string;
    checkpoint?: TaskCheckpoint;
    timestamp: string;
  };
  branchName?: string;
  conversationIncluded: boolean;
  messageCount?: number;
}

/** Request to resume supervisor */
export interface ResumeSupervisorRequest {
  resumeMode: "continue" | "restart_phase" | "from_checkpoint";
  checkpointId?: string;
  additionalContext?: string;
}

/** Response from supervisor resume */
export interface ResumeSupervisorResponse {
  supervisorTaskId: string;
  daemonId: string | null;
  contractId: string;
  phase: string;
  status: string;
  conversationMessageCount: number;
}

/** Request to rewind supervisor conversation */
export interface RewindConversationRequest {
  toMessageId?: string;
  byMessageCount?: number;
  rewindCode?: boolean;
}

/** Response from conversation rewind */
export interface RewindConversationResponse {
  contractId: string;
  messagesRemoved: number;
  newMessageCount: number;
}

// =============================================================================
// Resume/Rewind/Fork API Functions
// =============================================================================

/**
 * Rewind a task to a checkpoint.
 */
export async function rewindTask(
  taskId: string,
  request: RewindTaskRequest
): Promise<RewindTaskResponse> {
  const res = await authFetch(`${API_BASE}/api/v1/mesh/tasks/${taskId}/rewind`, {
    method: "POST",
    body: JSON.stringify(request),
  });
  if (!res.ok) {
    const errorText = await res.text();
    throw new Error(`Failed to rewind task: ${errorText || res.statusText}`);
  }
  return res.json();
}

/**
 * Fork a task from a checkpoint.
 */
export async function forkTask(
  taskId: string,
  request: ForkTaskRequest
): Promise<ForkTaskResponse> {
  const res = await authFetch(`${API_BASE}/api/v1/mesh/tasks/${taskId}/fork`, {
    method: "POST",
    body: JSON.stringify(request),
  });
  if (!res.ok) {
    const errorText = await res.text();
    throw new Error(`Failed to fork task: ${errorText || res.statusText}`);
  }
  return res.json();
}

/**
 * Resume a supervisor.
 */
export async function resumeSupervisor(
  contractId: string,
  request: ResumeSupervisorRequest
): Promise<ResumeSupervisorResponse> {
  const res = await authFetch(
    `${API_BASE}/api/v1/contracts/${contractId}/supervisor/resume`,
    {
      method: "POST",
      body: JSON.stringify(request),
    }
  );
  if (!res.ok) {
    const errorText = await res.text();
    throw new Error(`Failed to resume supervisor: ${errorText || res.statusText}`);
  }
  return res.json();
}

/**
 * Rewind supervisor conversation.
 */
export async function rewindSupervisorConversation(
  contractId: string,
  request: RewindConversationRequest
): Promise<RewindConversationResponse> {
  const res = await authFetch(
    `${API_BASE}/api/v1/contracts/${contractId}/supervisor/conversation/rewind`,
    {
      method: "POST",
      body: JSON.stringify(request),
    }
  );
  if (!res.ok) {
    const errorText = await res.text();
    throw new Error(`Failed to rewind conversation: ${errorText || res.statusText}`);
  }
  return res.json();
}

/**
 * Resume task from a checkpoint.
 */
export async function resumeFromCheckpoint(
  taskId: string,
  checkpointId: string,
  request: { taskName?: string; plan: string; includeConversation?: boolean }
): Promise<{ taskId: string }> {
  const res = await authFetch(
    `${API_BASE}/api/v1/mesh/tasks/${taskId}/checkpoints/${checkpointId}/resume`,
    {
      method: "POST",
      body: JSON.stringify(request),
    }
  );
  if (!res.ok) {
    const errorText = await res.text();
    throw new Error(`Failed to resume from checkpoint: ${errorText || res.statusText}`);
  }
  return res.json();
}

// =============================================================================
// Supervisor/Autopilot Control Functions
// =============================================================================

/**
 * Start a contract's supervisor task (enable autopilot mode).
 * This is a convenience wrapper around startTask.
 */
export async function startSupervisor(supervisorTaskId: string): Promise<Task> {
  return startTask(supervisorTaskId);
}

/**
 * Stop a contract's supervisor task (pause autopilot mode).
 * This is a convenience wrapper around stopTask.
 */
export async function stopSupervisor(supervisorTaskId: string): Promise<Task> {
  return stopTask(supervisorTaskId);
}

/** Status of the supervisor/autopilot for a contract */
export interface SupervisorStatus {
  supervisorTaskId: string | null;
  status: "not_configured" | "pending" | "starting" | "running" | "paused" | "done" | "failed";
  daemonId: string | null;
  canStart: boolean;
  canStop: boolean;
  canResume: boolean;
}

/**
 * Get the supervisor status for a contract.
 */
export function getSupervisorStatus(
  contract: ContractWithRelations
): SupervisorStatus {
  const supervisorTaskId = contract.supervisorTaskId;

  if (!supervisorTaskId) {
    return {
      supervisorTaskId: null,
      status: "not_configured",
      daemonId: null,
      canStart: false,
      canStop: false,
      canResume: false,
    };
  }

  // Find the supervisor task in the contract's tasks
  const supervisorTask = contract.tasks.find(
    (t) => t.id === supervisorTaskId && t.isSupervisor
  );

  if (!supervisorTask) {
    return {
      supervisorTaskId,
      status: "pending",
      daemonId: null,
      canStart: true,
      canStop: false,
      canResume: false,
    };
  }

  // Map task status to supervisor status
  let status: SupervisorStatus["status"];
  let canStart = false;
  let canStop = false;
  let canResume = false;

  switch (supervisorTask.status) {
    case "pending":
      status = "pending";
      canStart = true;
      break;
    case "initializing":
    case "starting":
      status = "starting";
      canStop = true;
      break;
    case "running":
      status = "running";
      canStop = true;
      break;
    case "paused":
    case "blocked":
      status = "paused";
      canResume = true;
      canStop = true;
      break;
    case "done":
    case "merged":
      status = "done";
      break;
    case "failed":
      status = "failed";
      canResume = true;
      break;
    default:
      status = "pending";
      canStart = true;
  }

  return {
    supervisorTaskId,
    status,
    daemonId: null, // Task summary doesn't have daemon_id, would need full task
    canStart,
    canStop,
    canResume,
  };
}
