"use client"; import { useState, useCallback, useMemo } from "react"; import { getWebSocketClient } from "@/lib/ws/connection"; // GitOperationResult matches the backend response export interface GitOperationResult { success: boolean; operation: string; output: string; error?: string; conflict_files?: string[]; } // PRCreateResult matches the backend PR creation response export interface PRCreateResult { success: boolean; pr_url?: string; output?: string; error?: string; } interface UseGitOperationsReturn { // Operation methods. The optional `repo` parameter is the multi-repo subpath // (e.g. "kandev"); pass empty/undefined for single-repo workspaces. Multi-repo // workspaces MUST scope each call to one repo — bulk callers fan out themselves. pull: (rebase?: boolean, repo?: string) => Promise; push: ( options?: { force?: boolean; setUpstream?: boolean }, repo?: string, ) => Promise; rebase: (baseBranch: string, repo?: string) => Promise; merge: (baseBranch: string, repo?: string) => Promise; abort: (operation: "merge" | "rebase", repo?: string) => Promise; commit: ( message: string, stageAll?: boolean, amend?: boolean, repo?: string, ) => Promise; stage: (paths?: string[], repo?: string) => Promise; unstage: (paths?: string[], repo?: string) => Promise; discard: (paths?: string[], repo?: string) => Promise; revertCommit: (commitSHA: string, repo?: string) => Promise; renameBranch: (newName: string) => Promise; reset: (commitSHA: string, mode: "soft" | "hard", repo?: string) => Promise; createPR: ( title: string, body: string, baseBranch?: string, draft?: boolean, repo?: string, ) => Promise; // State isLoading: boolean; loadingOperation: string | null; error: string | null; lastResult: GitOperationResult | null; } type ExecuteOperation = ( action: string, payload: Record, ) => Promise; function buildGitOperationCallbacks(executeOperation: ExecuteOperation) { const pull = async (rebase = false, repo?: string) => executeOperation("worktree.pull", { rebase, ...(repo ? { repo } : {}), }); const push = async (options?: { force?: boolean; setUpstream?: boolean }, repo?: string) => executeOperation("worktree.push", { force: options?.force ?? false, set_upstream: options?.setUpstream ?? false, ...(repo ? { repo } : {}), }); const rebase = async (baseBranch: string, repo?: string) => executeOperation("worktree.rebase", { base_branch: baseBranch, ...(repo ? { repo } : {}), }); const merge = async (baseBranch: string, repo?: string) => executeOperation("worktree.merge", { base_branch: baseBranch, ...(repo ? { repo } : {}), }); const abort = async (operation: "merge" | "rebase", repo?: string) => executeOperation("worktree.abort", { operation, ...(repo ? { repo } : {}), }); const commit = async (message: string, stageAll = true, amend = false, repo?: string) => executeOperation("worktree.commit", { message, stage_all: stageAll, amend, ...(repo ? { repo } : {}), }); const stage = async (paths?: string[], repo?: string) => executeOperation("worktree.stage", { paths: paths ?? [], ...(repo ? { repo } : {}), }); const unstage = async (paths?: string[], repo?: string) => executeOperation("worktree.unstage", { paths: paths ?? [], ...(repo ? { repo } : {}), }); const discard = async (paths?: string[], repo?: string) => executeOperation("worktree.discard", { paths: paths ?? [], ...(repo ? { repo } : {}), }); const revertCommit = async (commitSHA: string, repo?: string) => executeOperation("worktree.revert_commit", { commit_sha: commitSHA, ...(repo ? { repo } : {}), }); const renameBranch = async (newName: string) => executeOperation("worktree.rename_branch", { new_name: newName }); const reset = async (commitSHA: string, mode: "soft" | "hard", repo?: string) => executeOperation("worktree.reset", { commit_sha: commitSHA, mode, ...(repo ? { repo } : {}), }); const createPR = async ( title: string, body: string, baseBranch?: string, draft?: boolean, repo?: string, ): Promise => executeOperation("worktree.create_pr", { title, body, base_branch: baseBranch ?? "", draft: draft ?? true, ...(repo ? { repo } : {}), }); return { pull, push, rebase, merge, abort, commit, stage, unstage, discard, revertCommit, renameBranch, reset, createPR, }; } export function useGitOperations(sessionId: string | null): UseGitOperationsReturn { const [isLoading, setIsLoading] = useState(false); const [loadingOperation, setLoadingOperation] = useState(null); const [error, setError] = useState(null); const [lastResult, setLastResult] = useState(null); const executeOperation = useCallback( async ( action: string, payload: Record, ): Promise => { if (!sessionId) throw new Error("No session ID provided"); const client = getWebSocketClient(); if (!client) throw new Error("WebSocket not connected"); setIsLoading(true); setLoadingOperation(action.replace("worktree.", "")); setError(null); const timeout = action === "worktree.create_pr" ? 120000 : 60000; try { const result = await client.request( action, { session_id: sessionId, ...payload }, timeout, ); setLastResult(result); if (!result.success && result.error) setError(result.error); return result; } catch (e) { const errorMessage = e instanceof Error ? e.message : "Operation failed"; setError(errorMessage); throw e; } finally { setIsLoading(false); setLoadingOperation(null); } }, [sessionId], ); const ops = useMemo(() => buildGitOperationCallbacks(executeOperation), [executeOperation]); return { ...ops, isLoading, loadingOperation, error, lastResult, }; }