summaryrefslogblamecommitdiff
path: root/makima/frontend/src/lib/api.ts
blob: b7b904eb10aa5b932ed5659ffb6af03319249293 (plain) (tree)
1
2
2095
2096
2097
2098
2099
2100
2101
2102
2103
2104
2105
2106
2107
2108
2109
2110
2111
2112
2113
2114
2115
2116
2117
2118
2119
2120
2121
2122
2123
2124
2125
2126
2127
2128
2129
2130
2131
2132
2133
2134
2135
2136
2137
2138
2139
2140
2141
2142
2143
2144
2145
2146
2147
2148
2149
2150
2151
2152
2153
2154
2155
2156
2157
2158
2159
2160
2161
2162
2163
2164
2165
2166
2167
2168
2169
2170
2171
2172
2173
2174
2175
2176
2177
2178
2179
2180
2181
2182
2183
2184
2185
2186
2187
2188
2189
2190
2191
2192
2193
2194
2195
2196
2197
2198
2199
2200
2201
2202
2203
2204
2205
2206
2207
2208
2209
2210
2211
2212
2213
2214
2215
2216
2217
2218
2219
2220
2221
2222
2223
2224
2225
2226
2227
2228
2229
2230
2231
2232
2233
2234
2235
2236
2237
2238
2239
2240
2241
2242
2243
2244
2245
2246
2247
2248
2249
2250
2251
2252
2253
2254
2255
2256
2257
2258
2259
2260
2261
2262
2263
2264
2265
2266
2267
2268
2269
2270
2271
2272
2273
2274
2275
2276
2277
2278
2279
2280
2281
2282
2283
2284
2285
2286
2287
2288
2289
2290
2291
2292
2293
2294
2295
2296
2297
2298
2299
2300
2301
2302
2303
2304
2305
2306
2307
2308
2309
2310
2311
2312
2313
2314
2315
2316
2317
2318
2319
2320
2321
2322
2323
2324
2325
2326
2327
2328
2329
2330
2331
2332
2333
2334
2335
2336
2337
2338
2339
2340
2341
2342
2343
2344
2345
2346
2347
2348
2349
2350
2351
2352
2353
2354
2355
2356
2357
2358
2359
2360
2361
2362
2363
2364
2365
2366
2367
2368
2369
2370
2371
2372
2373
2374
2375
2376
2377
2378
2379
2380
2381
2382
2383
2384
2385
2386
2387
2388
2389
2390
2391
2392
2393
2394
2395
2396
2397
2398
2399
2400
2401
2402
2403
2404
2405
2406
2407
2408
2409
2410
2411
2412
2413
2414
2415
2416
2417
2418
2419
2420
2421
2422
2423
2424
2425
2426
2427
2428
2429
2430
2431
2432
2433
2434
2435
2436
2437
2438
2439
2440
2441
2442
2443
2444
2445
2446
2447
2448
2449
2450
2451
2452
2453
2454
2455
2456
2457
2458
2459
2460
2461
2462
2463
2464
2465
2466
2467
2468
2469
2470
2471
2472
2473
2474
2475
2476
2477
2478
2479
2480
2481
2482
2483
2484
2485
2486
2487
2488
2489
2490
2491

                                      


































                                                                                 






























































                                                                                     
                                                          
                                                        
                                                                           
                                                                                



                                               










                                  






                                                        

                                                        






                                       

                                                                  
 





                              
                  



                                                                     

                    



                                                








                                

                         
                  





                                                                     









                                    

                                                                                   

                       
                                 
                    

                                                      





                                    

                       




















                                                    

 


                                                                       


                                                              
                                                          






                                                                
                                                                






                                                                                
                                                           
                   











                                                                
                                                                 
                  

                               





                                                                 






                                                                
                                                                 





                                                                
 
                                                                      
































                                                                                          
                                                                             






                                                                                             
                                                                                        










                                                                
                                                                                      
                   













                                                                    
















                                                                                                      
































































                                                                                
























                                                                                





                                                     

                                                      






                                 

                                                                  

                                                                    







                       

                                      






































                                                               


                        











                                                

                                                                     





























                                                               

                                                                    







































































































































































                                                                                           









































































































                                                                                       

























































































                                                                                       




































































                                                                                      







                                                                                  










































                                                                                      





































































                                                                                                 






















                                                                                        
                                                                                



































                                                                                




































































                                                                                             


                                                                        


























































































































































































                                                                                    

                                                                                













































                                                                                


                                                                                
                                                                   
                                                                  




                                                                                   




                                                                             


                                   




                                                                            
                                               
                                                   
                    

 



                                                                        
 
















                                     

                                                   

                         

                                                      

                                                                                 











                             

                                                   



                                                      



                                                                                   

                                                                                 




























                                                         





                                                                                               

                                                                                 






                                        



                                               






































































































                                                                                

                                                                                          


                                          

                           


                                                                           
                                               































































































































































                                                                                


                                                                     

                                                                                








































                                                                                








                                                                                

                                                                      



                         

                                                 

                                                                                                                                                 










                                                                                     







































                                                                          









































































                                                                                       

                                                                                












































































































































































































































































































































































































                                                                                                

















































































































                                                                                               











                                                                                




























































                                                                                                                                                      









                                                                                                                                             








                                                                            













                                                                                                                                                                       









































                                                                                                          
 



                                                                                






                             





                                                                                              




                                    


                                  

                                                                

                                                                                     














                                



                                                                                            














                                                       


                                    

                                  

                                                                

                                                                                     















                                         


                                                                    


                         

                                                                     

                                                                                                     



                                         




                              

                                                               

                                                                                     

































































































































                                                                                                                                         

                                                                            
 

                                                                                                         

                   
                                                                                 


                    





                                       

































                                                                                       

















                                                                                  


























































                                                                                    





                                                                                                   







                                                                                              
                                                                                
                          
  









                                                                           

                                                                                
                                                                                             




                                                                            
 
                                    



                      
                                  




                            


                                                              



                    
                                                 



                 
                                                 

                 
                                         

 




                                                                                        


                    
                                              
                      


                                                                                         



                                                    
                                                                               


                    


                                                                                            



                    


                                                                     
   




                                                                             



                                                    








                                                                                                


                    





                                                                                     
                   

                                                    
     
                                                                                


                    











                                                                              
                                                               






































                                                                                               







                                                                                               



                                                                          




                         


                                                                      
   




                                                                                   



                                                                                














































































                                                                                                                          



                                                                                
                                                                                        












                                                                              
                               
                       
















                                     
                      
                        











                                     
                        







                                 
                  
                 





                                                          
                                           
                                        














































                                                                                                  
                                                                                   

                                                                                       




                                                                                     
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 SPEAK_ENDPOINT = `${WS_BASE}/api/v1/speak`;
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;
  }
}

// (LlmModel, ChatMessage, ChatRequest, UserQuestion, UserAnswer,
//  ChatResponse, ToolCallInfo removed alongside the LLM module —
//  see Chat API function block further down + the deleted /llm/ tree.)

// 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}`);
  }
}

// chatWithFile removed — handler `/api/v1/files/{id}/chat` is gone.

// 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;
  /** Whether this task is hidden from the UI (user dismissed it) */
  hidden: 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;
  /** Whether this task is hidden from the UI (user dismissed it) */
  hidden?: boolean;
  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();
}

// =============================================================================
// Git Actions for Tasks (Manual)
// =============================================================================

/** Response from export patch */
export interface ExportPatchResponse {
  success: boolean;
  taskId: string;
  fileName: string;
  filePath?: string;
  patchSize?: number;
  message: string;
}

/**
 * Export a task's changes as a patch file.
 * The patch will be saved to the contract's patch directory.
 */
export async function exportTaskPatch(
  taskId: string,
  fileName?: string
): Promise<ExportPatchResponse> {
  const body: Record<string, unknown> = {};
  if (fileName) {
    body.fileName = fileName;
  }
  const res = await authFetch(`${API_BASE}/api/v1/mesh/tasks/${taskId}/export-patch`, {
    method: "POST",
    body: JSON.stringify(body),
  });
  if (!res.ok) {
    const errorText = await res.text();
    throw new Error(`Failed to export patch: ${errorText || res.statusText}`);
  }
  return res.json();
}

/** Response from push branch */
export interface PushBranchResponse {
  success: boolean;
  taskId: string;
  branchName: string;
  remote?: string;
  message: string;
}

/**
 * Push a task's changes to a remote branch.
 * Creates a branch if it doesn't exist and pushes the commits.
 */
export async function pushTaskBranch(
  taskId: string,
  branchName?: string
): Promise<PushBranchResponse> {
  const body: Record<string, unknown> = {};
  if (branchName) {
    body.branchName = branchName;
  }
  const res = await authFetch(`${API_BASE}/api/v1/mesh/tasks/${taskId}/push-branch`, {
    method: "POST",
    body: JSON.stringify(body),
  });
  if (!res.ok) {
    const errorText = await res.text();
    throw new Error(`Failed to push branch: ${errorText || res.statusText}`);
  }
  return res.json();
}

/** Response from create PR */
export interface CreatePRResponse {
  success: boolean;
  taskId: string;
  prUrl?: string;
  prNumber?: number;
  branchName?: string;
  message: string;
}

/**
 * Create a pull request for a task's changes.
 * First pushes the branch if needed, then creates the PR.
 */
export async function createTaskPR(
  taskId: string,
  title?: string,
  body?: string
): Promise<CreatePRResponse> {
  const reqBody: Record<string, unknown> = {};
  if (title) {
    reqBody.title = title;
  }
  if (body) {
    reqBody.body = body;
  }
  const res = await authFetch(`${API_BASE}/api/v1/mesh/tasks/${taskId}/create-pr`, {
    method: "POST",
    body: JSON.stringify(reqBody),
  });
  if (!res.ok) {
    const errorText = await res.text();
    throw new Error(`Failed to create PR: ${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();
}

/** Response from the restart daemon endpoint */
export interface RestartDaemonResponse {
  success: boolean;
  daemonId: string;
  message: string;
}

/**
 * Restart a connected daemon.
 * Sends a restart command to the daemon, which will gracefully terminate
 * and restart. Any running tasks will be interrupted.
 */
export async function restartDaemon(id: string): Promise<RestartDaemonResponse> {
  const res = await authFetch(`${API_BASE}/api/v1/mesh/daemons/${id}/restart`, {
    method: "POST",
  });
  if (!res.ok) {
    const errorData = await res.json().catch(() => ({}));
    throw new Error(errorData.message || `Failed to restart daemon: ${res.statusText}`);
  }
  return res.json();
}

// =============================================================================
// Daemon Platform Download
// =============================================================================

/** A daemon platform with its availability and download URL */
export interface DaemonPlatform {
  platform: string;
  available: boolean;
  downloadUrl: string;
}

/** Response from the list daemon platforms endpoint */
export interface DaemonPlatformsResponse {
  platforms: DaemonPlatform[];
}

/**
 * List available daemon platforms and their download status.
 * This is an unauthenticated endpoint.
 */
export async function listDaemonPlatforms(): Promise<DaemonPlatformsResponse> {
  const res = await fetch(`${API_BASE}/api/v1/daemon/download/platforms`);
  if (!res.ok) {
    throw new Error(`Failed to list daemon platforms: ${res.statusText}`);
  }
  return res.json();
}

/**
 * Get the full download URL for a daemon binary.
 * Returns the absolute URL including API_BASE for cross-origin usage.
 */
export function getDaemonDownloadUrl(platform: string): string {
  return `${API_BASE}/api/v1/daemon/download/${platform}`;
}

// =============================================================================
// Daemon Reauthorization
// =============================================================================

/** Response from the trigger daemon reauth endpoint */
export interface TriggerReauthResponse {
  success: boolean;
  daemonId: string;
  requestId: string;
}

/** Response from the reauth status polling endpoint */
export interface ReauthStatusResponse {
  status: string; // "pending" | "url_ready" | "completed" | "failed"
  loginUrl?: string;
  error?: string;
}

/**
 * Trigger OAuth re-authentication on a daemon.
 * Sends a reauth command to the daemon, which will spawn `claude setup-token`
 * and return the OAuth login URL.
 */
export async function triggerDaemonReauth(daemonId: string): Promise<TriggerReauthResponse> {
  const res = await authFetch(`${API_BASE}/api/v1/mesh/daemons/${daemonId}/reauth`, {
    method: "POST",
  });
  if (!res.ok) {
    const errorData = await res.json().catch(() => ({}));
    throw new Error(errorData.message || `Failed to trigger reauth: ${res.statusText}`);
  }
  return res.json();
}

/**
 * Submit an OAuth auth code to a daemon's pending reauth flow.
 */
export async function submitDaemonAuthCode(
  daemonId: string,
  code: string,
  requestId: string,
): Promise<void> {
  const res = await authFetch(`${API_BASE}/api/v1/mesh/daemons/${daemonId}/reauth/code`, {
    method: "POST",
    body: JSON.stringify({ code, requestId }),
  });
  if (!res.ok) {
    const errorData = await res.json().catch(() => ({}));
    throw new Error(errorData.message || `Failed to submit auth code: ${res.statusText}`);
  }
}

/**
 * Get the status of a daemon reauth request.
 * Used for polling to track reauth flow progress.
 */
export async function getDaemonReauthStatus(
  daemonId: string,
  requestId: string,
): Promise<ReauthStatusResponse> {
  const res = await authFetch(
    `${API_BASE}/api/v1/mesh/daemons/${daemonId}/reauth/${requestId}/status`,
  );
  if (!res.ok) {
    const errorData = await res.json().catch(() => ({}));
    throw new Error(errorData.message || `Failed to get reauth status: ${res.statusText}`);
  }
  return res.json();
}

// (Mesh chat types + chatWithMesh / chatWithTask / getMeshChatHistory /
//  clearMeshChatHistory / chatWithMeshContext / MeshChatContext etc.
//  removed alongside the LLM module — backend handlers gone.)

// =============================================================================
// 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();
}

// =============================================================================
// User Settings (per-user feature flags)
// =============================================================================

/** Per-user settings / feature flags. */
export interface UserSettings {
  /** Whether the new "document mode" UI is enabled for this user. */
  documentModeEnabled: boolean;
}

/** Request body for updating user settings. */
export interface UpdateUserSettingsRequest {
  documentModeEnabled: boolean;
}

/**
 * Get the authenticated user's settings (feature flags).
 */
export async function getUserSettings(): Promise<UserSettings> {
  const res = await authFetch(`${API_BASE}/api/v1/users/me/settings`);
  if (!res.ok) {
    const errorData = await res.json().catch(() => null);
    const errorMessage = errorData?.message || res.statusText;
    throw new Error(errorMessage);
  }
  return res.json();
}

/**
 * Replace the authenticated user's settings (feature flags).
 */
export async function updateUserSettings(
  req: UpdateUserSettingsRequest
): Promise<UserSettings> {
  const res = await authFetch(`${API_BASE}/api/v1/users/me/settings`, {
    method: "PUT",
    body: JSON.stringify(req),
  });
  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" | "execute";
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"];
  }
  if (contractType === "execute") {
    return ["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";
  if (contractType === "execute") return "execute";
  return "research";
}

// (ContractTypeTemplate / ListContractTypesResponse / PhaseDefinition /
//  DeliverableDefinition / listContractTypes removed alongside the LLM
//  module — the templates handler was the only consumer and the
//  endpoint /api/v1/contract-types is gone.)

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;
  /** When true, tasks won't auto-push or create PRs - use patch files instead */
  localOnly: boolean;
  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;
  /** When true, tasks won't auto-push or create PRs - use patch files instead */
  localOnly: 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), "specification", "execute", or custom template name */
  contractType?: ContractType | string;
  /** UUID of a custom template to use. If provided, takes precedence over contractType. */
  templateId?: string;
  /** Initial phase to start in (defaults based on contract type or template) */
  initialPhase?: ContractPhase | string;
  /** When true, tasks won't auto-push or create PRs - use patch files instead */
  localOnly?: boolean;
}

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 / Contract Discussion API removed alongside the LLM
//  module — legacy contracts chat handlers were already Phase-5
//  removed; these were the dead frontend half.)

// =============================================================================
// 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;
  /** Directive this question relates to (if from a directive task) */
  directiveId?: string | null;
  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,
  };
}

// =============================================================================
// Task Dismiss (Hide) Functions
// =============================================================================

/**
 * Dismiss (hide) a completed standalone task from the UI.
 * This marks the task as hidden so it won't appear in the task list.
 */
export async function dismissTask(taskId: string): Promise<Task> {
  return updateTask(taskId, { hidden: true });
}

// =============================================================================
// Worktree Info Types and Functions
// =============================================================================

/** File status in the worktree (git status) */
export type FileStatus = "M" | "A" | "D" | "R" | "C" | "U" | "?" | "modified" | "added" | "deleted" | "renamed" | "copied" | "unmerged" | "untracked";

/** A single changed file in the worktree */
export interface WorktreeFile {
  /** File path relative to worktree root */
  path: string;
  /** Git status code (M=modified, A=added, D=deleted, R=renamed, C=copied, U=unmerged, ?=untracked) */
  status: FileStatus;
  /** Lines added (0 if deleted or unavailable) */
  linesAdded: number;
  /** Lines removed (0 if added or unavailable) */
  linesRemoved: number;
}

/** Statistics about worktree changes */
export interface WorktreeStats {
  /** Number of files changed */
  filesChanged: number;
  /** Total lines inserted */
  insertions: number;
  /** Total lines deleted */
  deletions: number;
}

/** Worktree information for a task */
export interface WorktreeInfo {
  /** Task ID */
  taskId: string;
  /** Path to the worktree directory */
  worktreePath: string | null;
  /** Whether the worktree exists on the daemon */
  exists: boolean;
  /** Aggregate statistics */
  stats: WorktreeStats;
  /** Changed files list */
  files: WorktreeFile[];
  /** Current branch name */
  branch: string | null;
  /** Current HEAD commit SHA */
  headSha: string | null;
}

/**
 * Get worktree information for a task.
 * Returns changed files, stats, and metadata about the worktree.
 */
export async function getWorktreeInfo(taskId: string): Promise<WorktreeInfo> {
  const res = await authFetch(`${API_BASE}/api/v1/mesh/tasks/${taskId}/worktree-info`);
  if (!res.ok) {
    const errorText = await res.text();
    throw new Error(`Failed to get worktree info: ${errorText || res.statusText}`);
  }
  return res.json();
}

/** Get the diff for a task's worktree changes */
export async function getTaskDiff(taskId: string): Promise<{ taskId: string; success: boolean; diff: string | null; error: string | null }> {
  const res = await authFetch(`${API_BASE}/api/v1/mesh/tasks/${taskId}/diff`);
  if (!res.ok) {
    const errorText = await res.text();
    throw new Error(`Failed to get task diff: ${errorText || res.statusText}`);
  }
  return res.json();
}

/** Get the worktree diff for a task, optionally scoped to a single file. */
export async function getWorktreeDiff(
  taskId: string,
  _filePath?: string,
): Promise<{ diff: string }> {
  const result = await getTaskDiff(taskId);
  return { diff: result.diff ?? "" };
}

/** Commit changes in a task's worktree */
export async function commitWorktree(taskId: string, message?: string): Promise<{ taskId: string; success: boolean; commitSha: string | null; error: string | null }> {
  const res = await authFetch(`${API_BASE}/api/v1/mesh/tasks/${taskId}/worktree-commit`, {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({ message }),
  });
  if (!res.ok) {
    const errorText = await res.text();
    throw new Error(`Failed to commit worktree: ${errorText || res.statusText}`);
  }
  return res.json();
}

// =============================================================================
// Patch Types and Functions
// =============================================================================

/** Summary of a patch file (contract file of type "patch") */
export interface PatchSummary {
  /** Patch/file ID */
  id: string;
  /** Patch name */
  name: string;
  /** Optional description */
  description: string | null;
  /** Task ID this patch was created from */
  taskId: string | null;
  /** Contract ID */
  contractId: string;
  /** Number of files in the patch */
  filesCount: number;
  /** Total lines added */
  linesAdded: number;
  /** Total lines removed */
  linesRemoved: number;
  /** List of file paths in the patch (if available) */
  files: string[] | null;
  /** When the patch was created */
  createdAt: string;
  /** When the patch was last updated */
  updatedAt: string;
}

/**
 * List patches for a task.
 * Returns contract files of type "patch" associated with the task.
 */
export async function listTaskPatches(taskId: string, contractId: string): Promise<PatchSummary[]> {
  const res = await authFetch(`${API_BASE}/api/v1/mesh/tasks/${taskId}/patches?contractId=${contractId}`);
  if (!res.ok) {
    const errorText = await res.text();
    throw new Error(`Failed to list patches: ${errorText || res.statusText}`);
  }
  return res.json();
}

// =============================================================================
// Directive Types & API
// =============================================================================

export type DirectiveStatus =
  | "draft"
  | "active"
  | "idle"
  | "paused"
  | "inactive"
  | "archived";
export type StepStatus = "pending" | "ready" | "running" | "completed" | "failed" | "skipped";

export interface Directive {
  id: string;
  ownerId: string;
  title: string;
  status: DirectiveStatus;
  repositoryUrl: string | null;
  localPath: string | null;
  baseBranch: string | null;
  orchestratorTaskId: string | null;
  prUrl: string | null;
  prBranch: string | null;
  completionTaskId: string | null;
  /** Whether the memory system is enabled for this directive */
  memoryEnabled: boolean;
  /** Reconcile mode: auto (timeout), semi-auto (pause), or manual (ask questions) */
  reconcileMode: string;
  startedAt: string | null;
  version: number;
  createdAt: string;
  updatedAt: string;
}

export interface DirectiveStep {
  id: string;
  directiveId: string;
  name: string;
  description: string | null;
  taskPlan: string | null;
  dependsOn: string[];
  status: StepStatus;
  taskId: string | null;
  /** Contract ID for contract-backed steps */
  contractId?: string | null;
  /** Contract type (e.g. "simple", "specification", "execute") for contract-backed steps */
  contractType?: string | null;
  orderIndex: number;
  generation: number;
  startedAt: string | null;
  completedAt: string | null;
  createdAt: string;
}

export interface DirectiveWithSteps extends Directive {
  steps: DirectiveStep[];
}

export interface DirectiveSummary {
  id: string;
  ownerId: string;
  title: string;
  status: DirectiveStatus;
  repositoryUrl: string | null;
  orchestratorTaskId: string | null;
  prUrl: string | null;
  completionTaskId: string | null;
  /** Whether the memory system is enabled for this directive */
  memoryEnabled: boolean;
  /** Reconcile mode: auto (timeout), semi-auto (pause), or manual (ask questions) */
  reconcileMode: string;
  version: number;
  createdAt: string;
  updatedAt: string;
  totalSteps: number;
  completedSteps: number;
  runningSteps: number;
  failedSteps: number;
}

export interface DirectiveListResponse {
  directives: DirectiveSummary[];
  total: number;
}

export interface CreateDirectiveRequest {
  title: string;
  /** Optional. When provided, a first contract is auto-created with
   *  this body so the directive is immediately ready to start. */
  contractBody?: string;
  repositoryUrl?: string;
  localPath?: string;
  baseBranch?: string;
  /** Enable the memory system for this directive (default: false) */
  memoryEnabled?: boolean;
  /** Reconcile mode: auto (timeout), semi-auto (pause), or manual (ask questions) (default: auto) */
  reconcileMode?: string;
}

export interface UpdateDirectiveRequest {
  title?: string;
  status?: string;
  repositoryUrl?: string;
  localPath?: string;
  baseBranch?: string;
  orchestratorTaskId?: string;
  /** Enable or disable the memory system for this directive */
  memoryEnabled?: boolean;
  /** Reconcile mode: auto (timeout), semi-auto (pause), or manual (ask questions) */
  reconcileMode?: string;
  version?: number;
}

export interface CreateDirectiveStepRequest {
  name: string;
  description?: string;
  taskPlan?: string;
  dependsOn?: string[];
  orderIndex?: number;
  generation?: number;
}

export interface UpdateDirectiveStepRequest {
  name?: string;
  description?: string;
  taskPlan?: string;
  dependsOn?: string[];
  status?: string;
  taskId?: string;
  orderIndex?: number;
}

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

export async function createDirective(req: CreateDirectiveRequest): Promise<Directive> {
  const res = await authFetch(`${API_BASE}/api/v1/directives`, {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify(req),
  });
  if (!res.ok) throw new Error(`Failed to create directive: ${res.statusText}`);
  return res.json();
}

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

export async function updateDirective(id: string, req: UpdateDirectiveRequest): Promise<Directive> {
  const res = await authFetch(`${API_BASE}/api/v1/directives/${id}`, {
    method: "PUT",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify(req),
  });
  if (!res.ok) throw new Error(`Failed to update directive: ${res.statusText}`);
  return res.json();
}

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

export async function createDirectiveStep(directiveId: string, req: CreateDirectiveStepRequest): Promise<DirectiveStep> {
  const res = await authFetch(`${API_BASE}/api/v1/directives/${directiveId}/steps`, {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify(req),
  });
  if (!res.ok) throw new Error(`Failed to create step: ${res.statusText}`);
  return res.json();
}

export async function batchCreateDirectiveSteps(directiveId: string, steps: CreateDirectiveStepRequest[]): Promise<DirectiveStep[]> {
  const res = await authFetch(`${API_BASE}/api/v1/directives/${directiveId}/steps/batch`, {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify(steps),
  });
  if (!res.ok) throw new Error(`Failed to batch create steps: ${res.statusText}`);
  return res.json();
}

export async function updateDirectiveStep(directiveId: string, stepId: string, req: UpdateDirectiveStepRequest): Promise<DirectiveStep> {
  const res = await authFetch(`${API_BASE}/api/v1/directives/${directiveId}/steps/${stepId}`, {
    method: "PUT",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify(req),
  });
  if (!res.ok) throw new Error(`Failed to update step: ${res.statusText}`);
  return res.json();
}

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

export async function startDirective(id: string): Promise<DirectiveWithSteps> {
  const res = await authFetch(`${API_BASE}/api/v1/directives/${id}/start`, { method: "POST" });
  if (!res.ok) throw new Error(`Failed to start directive: ${res.statusText}`);
  return res.json();
}

export async function pauseDirective(id: string): Promise<Directive> {
  const res = await authFetch(`${API_BASE}/api/v1/directives/${id}/pause`, { method: "POST" });
  if (!res.ok) throw new Error(`Failed to pause directive: ${res.statusText}`);
  return res.json();
}

export async function advanceDirective(id: string): Promise<DirectiveWithSteps> {
  const res = await authFetch(`${API_BASE}/api/v1/directives/${id}/advance`, { method: "POST" });
  if (!res.ok) throw new Error(`Failed to advance directive: ${res.statusText}`);
  return res.json();
}

export async function completeDirectiveStep(directiveId: string, stepId: string): Promise<DirectiveStep> {
  const res = await authFetch(`${API_BASE}/api/v1/directives/${directiveId}/steps/${stepId}/complete`, { method: "POST" });
  if (!res.ok) throw new Error(`Failed to complete step: ${res.statusText}`);
  return res.json();
}

export async function failDirectiveStep(directiveId: string, stepId: string): Promise<DirectiveStep> {
  const res = await authFetch(`${API_BASE}/api/v1/directives/${directiveId}/steps/${stepId}/fail`, { method: "POST" });
  if (!res.ok) throw new Error(`Failed to fail step: ${res.statusText}`);
  return res.json();
}

export async function skipDirectiveStep(directiveId: string, stepId: string): Promise<DirectiveStep> {
  const res = await authFetch(`${API_BASE}/api/v1/directives/${directiveId}/steps/${stepId}/skip`, { method: "POST" });
  if (!res.ok) throw new Error(`Failed to skip step: ${res.statusText}`);
  return res.json();
}

// (updateDirectiveGoal removed — spec edits go through the contracts API.
//  Use updateDirectiveContract(activeContractId, { body }) instead.)

export async function cleanupDirective(id: string): Promise<{ message: string; taskId: string | null }> {
  const res = await authFetch(`${API_BASE}/api/v1/directives/${id}/cleanup`, {
    method: "POST",
  });
  if (!res.ok) throw new Error(`Failed to cleanup directive: ${res.statusText}`);
  return res.json();
}

export interface PickUpOrdersResponse {
  message: string;
  orderCount: number;
  taskId: string | null;
}

/**
 * Per-PR snapshot of a directive's goal — frozen at PR creation, lifecycle
 * tracked alongside the PR itself.
 */
export interface DirectiveRevision {
  id: string;
  directiveId: string;
  /** Inline-markdown content of the directive goal at the moment the PR was raised. */
  content: string;
  prUrl: string;
  prBranch: string | null;
  /** "open" | "merged" | "closed" — tracks the PR lifecycle. */
  prState: string;
  version: number;
  frozenAt: string;
}

export interface DirectiveRevisionListResponse {
  revisions: DirectiveRevision[];
  total: number;
}

export async function listDirectiveRevisions(
  directiveId: string,
): Promise<DirectiveRevisionListResponse> {
  const res = await authFetch(
    `${API_BASE}/api/v1/directives/${directiveId}/revisions`,
  );
  if (!res.ok) {
    throw new Error(`Failed to list revisions: ${res.statusText}`);
  }
  return res.json();
}

/**
 * Reset a directive for a new draft cycle: clears its goal and detaches the
 * current PR linkage. Past revisions remain attached as history. Used by the
 * sidebar's "New draft" right-click on an inactive contract.
 */
export async function newDirectiveDraft(
  directiveId: string,
): Promise<Directive> {
  const res = await authFetch(
    `${API_BASE}/api/v1/directives/${directiveId}/new-draft`,
    { method: "POST" },
  );
  if (!res.ok) {
    throw new Error(`Failed to reset directive for new draft: ${res.statusText}`);
  }
  return res.json();
}

/**
 * Request body for spawning an ephemeral task under a directive. The task is
 * not part of the DAG — it lives alongside the directive's planned steps but
 * the orchestrator ignores it.
 */
export interface CreateDirectiveTaskRequest {
  name: string;
  plan: string;
  /** Override the directive's repository_url; defaults to the directive's. */
  repositoryUrl?: string;
  /** Override the directive's base_branch; defaults to the directive's. */
  baseBranch?: string;
}

/**
 * List ephemeral tasks (directive_id set, directive_step_id NULL) attached
 * to a directive. Backs the "spinoff" group inside each directive folder
 * in the document-mode sidebar.
 */
export async function listDirectiveEphemeralTasks(
  directiveId: string,
): Promise<TaskListResponse> {
  const res = await authFetch(`${API_BASE}/api/v1/directives/${directiveId}/tasks`);
  if (!res.ok) {
    throw new Error(`Failed to list directive tasks: ${res.statusText}`);
  }
  return res.json();
}

export async function createDirectiveTask(
  directiveId: string,
  req: CreateDirectiveTaskRequest,
): Promise<Task> {
  const res = await authFetch(
    `${API_BASE}/api/v1/directives/${directiveId}/tasks`,
    {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify(req),
    },
  );
  if (!res.ok) {
    throw new Error(`Failed to create directive task: ${res.statusText}`);
  }
  return res.json();
}

/**
 * List tasks not attached to any directive (and not subtasks). Backs the
 * `tmp/` pseudo-folder in the document-mode sidebar.
 */
export async function listOrphanTasks(): Promise<TaskListResponse> {
  const res = await authFetch(`${API_BASE}/api/v1/mesh/tasks?orphan=true`);
  if (!res.ok) {
    throw new Error(`Failed to list orphan tasks: ${res.statusText}`);
  }
  return res.json();
}

export async function createDirectivePR(id: string): Promise<DirectiveWithSteps> {
  const res = await authFetch(`${API_BASE}/api/v1/directives/${id}/create-pr`, { method: "POST" });
  if (!res.ok) throw new Error(`Failed to create PR: ${res.statusText}`);
  return res.json();
}

export async function pickUpOrders(directiveId: string): Promise<PickUpOrdersResponse> {
  const res = await authFetch(`${API_BASE}/api/v1/directives/${directiveId}/pick-up-orders`, {
    method: "POST",
  });
  if (!res.ok) throw new Error(`Failed to pick up orders: ${res.statusText}`);
  return res.json();
}

// =============================================================================
// Directive Contracts API
//
// A "directive contract" (DB table: directive_documents) is one of N spec
// documents owned by a directive. Each has its own lifecycle
// (draft → active → shipped → archived) and may be attached to a PR.
// Contracts run sequentially in the directive's shared worktree (or, when
// mergeMode = 'own_pr', on a contract-specific branch).
//
// Naming note: types are `DirectiveContract*` (rather than `Contract*`)
// because the legacy contracts system still defines a `Contract` type at
// the top of this file. Once Phase 5 drops legacy contracts, we'll rename
// to plain `Contract`. URLs and the user-facing UI already say "contract".
// =============================================================================

export type DirectiveContractStatus = "draft" | "queued" | "active" | "shipped" | "archived";

/** How a contract's commits land. `shared` (default): on the directive's
 *  branch — multiple contracts feed one PR. `own_pr`: a contract-specific
 *  branch + PR carved out at activation time. */
export type DirectiveContractMergeMode = "shared" | "own_pr";

export interface DirectiveContract {
  id: string;
  directiveId: string;
  title: string;
  body: string;
  status: DirectiveContractStatus;
  prUrl: string | null;
  prBranch: string | null;
  shippedAt: string | null;
  archivedAt: string | null;
  version: number;
  /** 0-indexed queue position within the parent directive. */
  position: number;
  mergeMode: DirectiveContractMergeMode;
  createdAt: string;
  updatedAt: string;
}

export interface CreateDirectiveContractRequest {
  title?: string;
  body?: string;
}

export interface UpdateDirectiveContractRequest {
  title?: string;
  body?: string;
  mergeMode?: DirectiveContractMergeMode;
}

export async function listDirectiveContracts(
  directiveId: string,
): Promise<DirectiveContract[]> {
  const res = await authFetch(`${API_BASE}/api/v1/directives/${directiveId}/contracts`);
  if (!res.ok) throw new Error(`Failed to list contracts: ${res.statusText}`);
  return res.json();
}

export async function createDirectiveContract(
  directiveId: string,
  req: CreateDirectiveContractRequest = {},
): Promise<DirectiveContract> {
  const res = await authFetch(`${API_BASE}/api/v1/directives/${directiveId}/contracts`, {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify(req),
  });
  if (!res.ok) throw new Error(`Failed to create contract: ${res.statusText}`);
  return res.json();
}

export async function getDirectiveContract(contractId: string): Promise<DirectiveContract> {
  const res = await authFetch(`${API_BASE}/api/v1/contracts/${contractId}`);
  if (!res.ok) throw new Error(`Failed to get contract: ${res.statusText}`);
  return res.json();
}

/**
 * Update a contract's title, body, and/or merge mode. Backend auto-
 * reactivates a shipped contract when its body changes (status flips
 * back to `active`), so there's no separate reactivate call.
 */
export async function updateDirectiveContract(
  contractId: string,
  req: UpdateDirectiveContractRequest,
): Promise<DirectiveContract> {
  const res = await authFetch(`${API_BASE}/api/v1/contracts/${contractId}`, {
    method: "PATCH",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify(req),
  });
  if (!res.ok) throw new Error(`Failed to update contract: ${res.statusText}`);
  return res.json();
}

export async function archiveDirectiveContract(contractId: string): Promise<DirectiveContract> {
  const res = await authFetch(`${API_BASE}/api/v1/contracts/${contractId}/archive`, {
    method: "POST",
  });
  if (!res.ok) throw new Error(`Failed to archive contract: ${res.statusText}`);
  return res.json();
}

/** Move a contract to a new 0-indexed queue position inside its directive. */
export async function reorderDirectiveContract(
  contractId: string,
  position: number,
): Promise<DirectiveContract> {
  const res = await authFetch(`${API_BASE}/api/v1/contracts/${contractId}/reorder`, {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({ position }),
  });
  if (!res.ok) throw new Error(`Failed to reorder contract: ${res.statusText}`);
  return res.json();
}

// ----- Lifecycle transitions -----------------------------------------------
//
// Lock & Start: draft → queued (if a sibling is active) or active.
// Pause: active → queued; next queued sibling auto-promotes.
// Complete: active → shipped, optionally records pr_url + pr_branch.
// Unlock: queued or active → draft (reactivates editing).
//
// All four reach the same /contracts/{id}/<action> endpoint and surface
// 400 with a plain message string when the transition is invalid.

async function postContractAction(
  contractId: string,
  action: "start" | "pause" | "complete" | "unlock" | "reopen",
  body?: object,
): Promise<DirectiveContract> {
  const res = await authFetch(`${API_BASE}/api/v1/contracts/${contractId}/${action}`, {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify(body ?? {}),
  });
  if (!res.ok) {
    let detail = res.statusText;
    try {
      const err = await res.json();
      if (err?.message) detail = err.message;
    } catch {
      /* fall through to statusText */
    }
    throw new Error(`Failed to ${action} contract: ${detail}`);
  }
  return res.json();
}

export async function startDirectiveContract(contractId: string): Promise<DirectiveContract> {
  return postContractAction(contractId, "start");
}

export async function pauseDirectiveContract(contractId: string): Promise<DirectiveContract> {
  return postContractAction(contractId, "pause");
}

export async function completeDirectiveContract(
  contractId: string,
  opts: { prUrl?: string; prBranch?: string } = {},
): Promise<DirectiveContract> {
  return postContractAction(contractId, "complete", opts);
}

export async function unlockDirectiveContract(contractId: string): Promise<DirectiveContract> {
  return postContractAction(contractId, "unlock");
}

/** Reopen a shipped contract for amendment. Backend flips the contract
 *  to `active`, re-activates the directive, and clears the prior PR
 *  linkage so the orchestrator replans against the contract body with
 *  an amendment framing. */
export async function reopenDirectiveContract(contractId: string): Promise<DirectiveContract> {
  return postContractAction(contractId, "reopen");
}

/** Steps and tasks attached to a single contract. Drives the per-contract
 *  `tasks/` subfolder in the sidebar — when the contract ships, its
 *  tasks visually move with it. */
export interface DirectiveContractTasksResponse {
  steps: DirectiveStep[];
  tasks: Task[];
}

/**
 * List the steps and ephemeral tasks attached to a specific contract.
 * Used by the sidebar to render a `tasks/` subfolder beside each
 * contract — including shipped ones, whose tasks remain attached.
 */
export async function listDirectiveContractTasks(
  contractId: string,
): Promise<DirectiveContractTasksResponse> {
  const res = await authFetch(`${API_BASE}/api/v1/contracts/${contractId}/tasks`);
  if (!res.ok) throw new Error(`Failed to list contract tasks: ${res.statusText}`);
  return res.json();
}

// =============================================================================
// Directive Order Groups (DOGs) API
// =============================================================================

export type DOGStatus = "open" | "in_progress" | "done" | "archived";

export interface DirectiveOrderGroup {
  id: string;
  directiveId: string;
  ownerId: string;
  name: string;
  description: string | null;
  status: DOGStatus;
  createdAt: string;
  updatedAt: string;
}

export interface DOGListResponse {
  dogs: DirectiveOrderGroup[];
}

export interface CreateDOGRequest {
  name: string;
  description?: string | null;
}

export interface UpdateDOGRequest {
  name?: string;
  description?: string | null;
  status?: DOGStatus;
}

export async function listDogs(directiveId: string): Promise<DOGListResponse> {
  const res = await authFetch(`${API_BASE}/api/v1/directives/${directiveId}/dogs`);
  if (!res.ok) throw new Error(`Failed to list DOGs: ${res.statusText}`);
  return res.json();
}

export async function createDog(directiveId: string, req: CreateDOGRequest): Promise<DirectiveOrderGroup> {
  const res = await authFetch(`${API_BASE}/api/v1/directives/${directiveId}/dogs`, {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify(req),
  });
  if (!res.ok) throw new Error(`Failed to create DOG: ${res.statusText}`);
  return res.json();
}

export async function getDog(directiveId: string, dogId: string): Promise<DirectiveOrderGroup> {
  const res = await authFetch(`${API_BASE}/api/v1/directives/${directiveId}/dogs/${dogId}`);
  if (!res.ok) throw new Error(`Failed to get DOG: ${res.statusText}`);
  return res.json();
}

export async function updateDog(directiveId: string, dogId: string, req: UpdateDOGRequest): Promise<DirectiveOrderGroup> {
  const res = await authFetch(`${API_BASE}/api/v1/directives/${directiveId}/dogs/${dogId}`, {
    method: "PATCH",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify(req),
  });
  if (!res.ok) throw new Error(`Failed to update DOG: ${res.statusText}`);
  return res.json();
}

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

export async function pickUpDogOrders(directiveId: string, dogId: string): Promise<PickUpOrdersResponse> {
  const res = await authFetch(`${API_BASE}/api/v1/directives/${directiveId}/dogs/${dogId}/pick-up-orders`, {
    method: "POST",
  });
  if (!res.ok) throw new Error(`Failed to pick up DOG orders: ${res.statusText}`);
  return res.json();
}

// =============================================================================
// Orders API
// =============================================================================

export type OrderPriority = "critical" | "high" | "medium" | "low" | "none";
export type OrderStatus = "open" | "in_progress" | "under_review" | "done" | "archived";
export type OrderType = "feature" | "bug" | "spike" | "chore" | "improvement";

export interface Order {
  id: string;
  ownerId: string;
  title: string;
  description: string | null;
  priority: OrderPriority;
  status: OrderStatus;
  orderType: OrderType;
  labels: string[];
  directiveId: string | null;
  directiveStepId: string | null;
  directiveName: string | null;
  dogId: string | null;
  repositoryUrl: string | null;
  createdAt: string;
  updatedAt: string;
}

export interface OrderListResponse {
  orders: Order[];
  total: number;
}

export interface CreateOrderRequest {
  title: string;
  description?: string | null;
  priority?: OrderPriority;
  status?: OrderStatus;
  orderType?: OrderType;
  labels?: string[];
  directiveId: string;
  dogId?: string | null;
  repositoryUrl?: string | null;
}

export interface UpdateOrderRequest {
  title?: string;
  description?: string | null;
  priority?: OrderPriority;
  status?: OrderStatus;
  orderType?: OrderType;
  labels?: string[];
  directiveId?: string | null;
  directiveStepId?: string | null;
  dogId?: string | null;
  repositoryUrl?: string | null;
}

export async function listOrders(
  status?: OrderStatus,
  type?: OrderType,
  priority?: OrderPriority,
  directiveId?: string,
  search?: string,
  dogId?: string,
): Promise<OrderListResponse> {
  const params = new URLSearchParams();
  if (status) params.set("status", status);
  if (type) params.set("type", type);
  if (priority) params.set("priority", priority);
  if (directiveId) params.set("directiveId", directiveId);
  if (search) params.set("search", search);
  if (dogId) params.set("dogId", dogId);
  const qs = params.toString();
  const res = await authFetch(`${API_BASE}/api/v1/orders${qs ? `?${qs}` : ""}`);
  if (!res.ok) throw new Error(`Failed to list orders: ${res.statusText}`);
  return res.json();
}

export async function createOrder(req: CreateOrderRequest): Promise<Order> {
  const res = await authFetch(`${API_BASE}/api/v1/orders`, {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify(req),
  });
  if (!res.ok) throw new Error(`Failed to create order: ${res.statusText}`);
  return res.json();
}

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

export async function updateOrder(id: string, req: UpdateOrderRequest): Promise<Order> {
  const res = await authFetch(`${API_BASE}/api/v1/orders/${id}`, {
    method: "PATCH",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify(req),
  });
  if (!res.ok) throw new Error(`Failed to update order: ${res.statusText}`);
  return res.json();
}

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

export async function linkOrderToDirective(orderId: string, directiveId: string): Promise<Order> {
  const res = await authFetch(`${API_BASE}/api/v1/orders/${orderId}/link-directive`, {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({ directiveId }),
  });
  if (!res.ok) throw new Error(`Failed to link order to directive: ${res.statusText}`);
  return res.json();
}

export async function convertOrderToStep(orderId: string): Promise<DirectiveStep> {
  const res = await authFetch(`${API_BASE}/api/v1/orders/${orderId}/convert-to-step`, {
    method: "POST",
  });
  if (!res.ok) throw new Error(`Failed to convert order to step: ${res.statusText}`);
  return res.json();
}