summaryrefslogblamecommitdiff
path: root/makima/frontend/src/lib/api.ts
blob: 445537c2cf727ea18e660ef135f333ce7d64daa5 (plain) (tree)
1
2
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283
1284
1285
1286
1287
1288
1289
1290
1291
1292
1293
1294
1295
1296
1297
1298
1299
1300
1301
1302
1303
1304
1305
1306
1307
1308
1309
1310
1311
1312
1313
1314
1315
1316
1317
1318
1319
1320
1321
1322
1323
1324
1325
1326
1327
1328
1329
1330
1331
1332
1333
1334
1335
1336
1337
1338
1339
1340
1341
1342
1343
1344
1345
1346
1347
1348
1349
1350
1351
1352
1353
1354
1355
1356
1357
1358
1359
1360
1361
1362
1363
1364
1365
1366
1367
1368
1369
1370
1371
1372
1373
1374
1375
1376
1377
1378
1379
1380
1381
1382
1383
1384
1385
1386
1387
1388
1389
1390
1391
1392
1393
1394
1395
1396
1397
1398
1399
1400
1401
1402
1403
1404
1405
1406
1407
1408
1409
1410
1411
1412
1413
1414
1415
1416
1417
1418
1419
1420
1421
1422
1423
1424
1425
1426
1427
1428
1429
1430
1431
1432
1433
1434
1435
1436
1437
1438
1439
1440
1441
1442
1443
1444
1445
1446
1447
1448
1449
1450
1451
1452
1453
1454
1455
1456
1457
1458
1459
1460
1461
1462
1463
1464
1465
1466
1467
1468
1469
1470
1471
1472
1473
1474
1475
1476
1477
1478
1479
1480
1481
1482
1483
1484
1485
1486
1487
1488
1489
1490
1491
1492
1493
1494
1495
1496
1497
1498
1499
1500
1501
1502
1503
1504
1505
1506
1507
1508
1509
1510
1511
1512
1513
1514
1515
1516
1517
1518
1519
1520
1521
1522
1523
1524
1525
1526
1527
1528
1529
1530
1531
1532
1533
1534
1535
1536
1537
1538
1539
1540
1541
1542
1543
1544
1545
1546
1547
1548
1549
1550
1551
1552
1553
1554
1555
1556
1557
1558
1559
1560
1561
1562
1563
1564
1565
1566
1567
1568
1569
1570
1571
1572
1573
1574
1575
1576
1577
1578
1579
1580
1581
1582
1583
1584
1585
1586
1587
1588
1589
1590
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
2492
2493
2494
2495
2496
2497
2498
2499
2500
2501
2502
2503
2504
2505
2506
2507
2508
2509
2510
2511
2512
2513
2514
2515
2516
2517
2518
2519
2520
2521
2522
2523
2524
2525
2526
2527
2528
2529
2530
2531
2532
2533
2534
2535
2536
2537
2538
2539
2540
2541
2542
2543
2544
2545
2546
2547
2548
2549
2550
2551
2552
2553
2554
2555
2556
2557
2558
2559
2560
2561
2562
2563
2564
2565
2566
2567
2568
2569
2570
2571
2572
2573
2574
2575
2576
2577
2578
2579
2580
2581
2582
2583
2584
2585
2586
2587
2588
2589
2590
2591
2592
2593
2594
2595
2596
2597
2598
2599
2600
2601
2602
2603
2604
2605
2606
2607
2608
2609
2610
2611
2612
2613
2614
2615
2616
2617
2618
2619
2620
2621
2622
2623
2624
2625
2626
2627
2628
2629
2630
2631
2632
2633
2634
2635
2636
2637
2638
2639
2640
2641
2642
2643
2644
2645
2646
2647
2648
2649
2650
2651
2652
2653
2654
2655
2656
2657
2658
2659
2660
2661
2662
2663
2664
2665
2666
2667
2668
2669
2670
2671
2672
2673
2674
2675
2676
2677
2678
2679
2680
2681
2682
2683
2684
2685
2686
2687
2688
2689
2690
2691
2692
2693
2694
2695
2696
2697
2698
2699
2700
2701
2702
2703
2704
2705
2706
2707
2708
2709
2710
2711
2712
2713
2714
2715
2716
2717
2718
2719
2720
2721

                                      


































                                                                                 






























































                                                                                     
                                                          
                                                        
                                                                           
                                                                                



                                               










                                  






                                                        

                                                        






                                       

                                                                  
 





                              
                  



                                                                     

                    



                                                








                                

                         
                  





                                                                     









                                    

                                                                                   

                       
                                 
                    

                                                      





                                    

                       




















                                                    

 


                                                                
                 




                              

                              
                   
                          
                               









                               













                                               




                                
                                    



                                                              
                                                          






                                                                
                                                                






                                                                                
                                                           
                   











                                                                
                                                                 
                  

                               





                                                                 






                                                                
                                                                 





                                                                



                                   
                  
                   

                              
                          



                                        


                                      



                                                                      
                   
                               






                                                                   
































                                                                                          
                                                                             






                                                                                             
                                                                                        










                                                                
                                                                                      
                   













                                                                    
















                                                                                                      
































































                                                                                
























                                                                                





                                                     

                                                      






                                 

                                                                  

                                                                    







                       

                                      






































                                                               


                        











                                                

                                                                     





























                                                               

                                                                    







































































































































































                                                                                           









































































































                                                                                       

























































































                                                                                       




































































                                                                                      







                                                                                  










































                                                                                      





































































                                                                                                 






















                                                                                        














































































































































































































































































































































































                                                                                                     




                                                                                
                                                                   
                                                                  




                                                                                   




                                                                             


                                   




                                                                            
                                               
                                                   
                    

 

















                                                                                

                                                       



                                                 
                                        

 













                                                    
   

                                                                








                                                                               
















                                     

                                                   

                         

                                                      

                                                                                 











                             

                                                   



                                                      



                                                                                   

                                                                                 




























                                                         





                                                                                               

                                                                                 






                                        



                                               






































































































                                                                                

                                                                                          


                                          

                           


                                                                           
                                               







































































































































































































































































































                                                                                  












                                                                                

                                                 

                                                                                                                                                 










                                                                                     







































                                                                          









































































                                                                                       

                                                                                












































































































































































































































































































































































































                                                                                                

















































































































                                                                                               











                                                                                






































































































                                                                                                                                                      











































































































































































































































                                                                                     
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;
  }
}

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

export interface RestoreVersionRequest {
  targetVersion: number;
}

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

// Mesh Chat API functions

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

/** Contract type determines the workflow and required documents */
export type ContractType = "simple" | "specification" | "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";
}

// =============================================================================
// Contract Type Templates
// =============================================================================

/** Contract type template returned by the API */
export interface ContractTypeTemplate {
  /** Template identifier (e.g., "simple", "specification") */
  id: string;
  /** Display name */
  name: string;
  /** Description of the contract type workflow */
  description: string;
  /** Ordered list of phases for this contract type */
  phases: ContractPhase[];
  /** Default starting phase */
  defaultPhase: ContractPhase;
  /** Whether this is a built-in type (always available) */
  isBuiltin: boolean;
  /** Optional mapping from phase ID to display name */
  phaseNames?: Record<string, string>;
}

/** Response from list contract types endpoint */
export interface ListContractTypesResponse {
  contractTypes: ContractTypeTemplate[];
}

/** Phase definition for custom templates */
export interface PhaseDefinition {
  id: string;
  name: string;
  order: number;
}

/** Deliverable definition for custom templates */
export interface DeliverableDefinition {
  id: string;
  name: string;
  priority: "required" | "recommended" | "optional";
}

/**
 * List available contract types.
 * Returns built-in types only (simple, specification, execute).
 */
export async function listContractTypes(): Promise<ListContractTypesResponse> {
  const res = await authFetch(`${API_BASE}/api/v1/contract-types`);
  if (!res.ok) {
    throw new Error(`Failed to list contract types: ${res.statusText}`);
  }
  return res.json();
}

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 Types and API
// =============================================================================

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

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

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

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

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

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

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

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

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

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

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

export interface ListTemplatesResponse {
  templates: TemplateSummary[];
}

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

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

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

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

export interface AnswerQuestionRequest {
  response: string;
}

export interface AnswerQuestionResponse {
  success: boolean;
}

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

// =============================================================================
// Chain Types and API
// =============================================================================

/** Chain status */
export type ChainStatus = "active" | "completed" | "archived";

/** Chain summary for list view */
export interface ChainSummary {
  id: string;
  name: string;
  description: string | null;
  status: ChainStatus;
  contractCount: number;
  completedContractCount: number;
  loopEnabled: boolean;
  loopCurrentIteration: number | null;
  loopMaxIterations: number | null;
  createdAt: string;
  updatedAt: string;
}

/** Full chain with contracts */
export interface Chain {
  id: string;
  ownerId: string;
  name: string;
  description: string | null;
  status: ChainStatus;
  loopEnabled: boolean;
  loopMaxIterations: number | null;
  loopCurrentIteration: number | null;
  loopProgressCheck: string | null;
  repositoryUrl: string | null;
  localPath: string | null;
  version: number;
  createdAt: string;
  updatedAt: string;
}

/** Contract detail within a chain */
export interface ChainContractDetail {
  id: string;
  chainId: string;
  contractId: string;
  contractName: string;
  contractStatus: string;
  contractPhase: string;
  dependsOn: string[];
  orderIndex: number;
  editorX: number | null;
  editorY: number | null;
  createdAt: string;
}

/** Chain with contracts */
export interface ChainWithContracts {
  chain: Chain;
  contracts: ChainContractDetail[];
}

/** Node in chain graph visualization */
export interface ChainGraphNode {
  id: string;
  contractId: string;
  name: string;
  status: string;
  phase: string;
  x: number;
  y: number;
}

/** Edge in chain graph */
export interface ChainGraphEdge {
  from: string;
  to: string;
}

/** Chain graph response */
export interface ChainGraphResponse {
  chainId: string;
  chainName: string;
  chainStatus: string;
  nodes: ChainGraphNode[];
  edges: ChainGraphEdge[];
}

/** Chain event */
export interface ChainEvent {
  id: string;
  chainId: string;
  eventType: string;
  contractId: string | null;
  eventData: Record<string, unknown> | null;
  createdAt: string;
}

/** Chain list response */
export interface ChainListResponse {
  chains: ChainSummary[];
  total: number;
}

/** Create chain request */
export interface CreateChainRequest {
  name: string;
  description?: string;
  repositoryUrl?: string;
  localPath?: string;
  loopEnabled?: boolean;
  loopMaxIterations?: number;
  loopProgressCheck?: string;
  contracts?: CreateChainContractRequest[];
}

/** Create chain contract request */
export interface CreateChainContractRequest {
  name: string;
  description?: string;
  contractType?: string;
  initialPhase?: string;
  phases?: string[];
  dependsOn?: string[];
  tasks?: { name: string; plan: string }[];
  deliverables?: { id: string; name: string; priority?: string }[];
  editorX?: number;
  editorY?: number;
}

/** Update chain request */
export interface UpdateChainRequest {
  name?: string;
  description?: string;
  status?: ChainStatus;
  loopEnabled?: boolean;
  loopMaxIterations?: number;
  loopProgressCheck?: string;
  version?: number;
}

/** List chains */
export async function listChains(
  status?: ChainStatus,
  limit = 50,
  offset = 0
): Promise<ChainListResponse> {
  const params = new URLSearchParams();
  if (status) params.set("status", status);
  params.set("limit", String(limit));
  params.set("offset", String(offset));

  const res = await authFetch(`${API_BASE}/api/v1/chains?${params}`);
  if (!res.ok) {
    throw new Error(`Failed to list chains: ${res.statusText}`);
  }
  return res.json();
}

/** Get chain by ID */
export async function getChain(chainId: string): Promise<ChainWithContracts> {
  const res = await authFetch(`${API_BASE}/api/v1/chains/${chainId}`);
  if (!res.ok) {
    throw new Error(`Failed to get chain: ${res.statusText}`);
  }
  return res.json();
}

/** Create a new chain */
export async function createChain(req: CreateChainRequest): Promise<Chain> {
  const res = await authFetch(`${API_BASE}/api/v1/chains`, {
    method: "POST",
    body: JSON.stringify(req),
  });
  if (!res.ok) {
    const errorText = await res.text();
    throw new Error(`Failed to create chain: ${errorText || res.statusText}`);
  }
  return res.json();
}

/** Update a chain */
export async function updateChain(
  chainId: string,
  req: UpdateChainRequest
): Promise<Chain> {
  const res = await authFetch(`${API_BASE}/api/v1/chains/${chainId}`, {
    method: "PUT",
    body: JSON.stringify(req),
  });
  if (!res.ok) {
    const errorText = await res.text();
    throw new Error(`Failed to update chain: ${errorText || res.statusText}`);
  }
  return res.json();
}

/** Archive a chain */
export async function archiveChain(chainId: string): Promise<{ archived: boolean }> {
  const res = await authFetch(`${API_BASE}/api/v1/chains/${chainId}`, {
    method: "DELETE",
  });
  if (!res.ok) {
    throw new Error(`Failed to archive chain: ${res.statusText}`);
  }
  return res.json();
}

/** Get chain contracts */
export async function getChainContracts(
  chainId: string
): Promise<ChainContractDetail[]> {
  const res = await authFetch(`${API_BASE}/api/v1/chains/${chainId}/contracts`);
  if (!res.ok) {
    throw new Error(`Failed to get chain contracts: ${res.statusText}`);
  }
  return res.json();
}

/** Get chain graph for visualization */
export async function getChainGraph(chainId: string): Promise<ChainGraphResponse> {
  const res = await authFetch(`${API_BASE}/api/v1/chains/${chainId}/graph`);
  if (!res.ok) {
    throw new Error(`Failed to get chain graph: ${res.statusText}`);
  }
  return res.json();
}

/** Get chain events */
export async function getChainEvents(chainId: string): Promise<ChainEvent[]> {
  const res = await authFetch(`${API_BASE}/api/v1/chains/${chainId}/events`);
  if (!res.ok) {
    throw new Error(`Failed to get chain events: ${res.statusText}`);
  }
  return res.json();
}