import { useMutation, useQuery, useQueryClient, type UseMutationResult, type UseQueryResult, } from "@tanstack/react-query"; import { apiClient } from "@/lib/api/client"; export interface FileEntry { name: string; path: string; isDir: boolean; size?: number; modTime?: string; language?: string; } const LANGUAGE_BY_EXTENSION: Record = { css: "css", go: "go", html: "html", js: "javascript", jsx: "javascript", json: "json", md: "markdown", py: "python", ts: "typescript", tsx: "typescript", yaml: "yaml", yml: "yaml", }; export function inferLanguage(name: string): string | undefined { const ext = name.split(".").pop()?.toLowerCase(); return ext ? LANGUAGE_BY_EXTENSION[ext] : undefined; } export interface ReadFileResponse { path: string; content: string; } function fileListQueryKey(workspaceId: string, path: string) { return ["workspaces", workspaceId, "files", path || "."] as const; } async function fetchFileList( workspaceId: string, path: string, ): Promise { const queryPath = path || "."; const url = `/api/workspaces/${encodeURIComponent(workspaceId)}/files?` + new URLSearchParams({ path: queryPath }).toString(); const res = await apiClient(url); if (!res.ok) { throw new Error(`Failed to list files: ${res.status}`); } return (await res.json()) as FileEntry[]; } export function fileReadQueryKey(workspaceId: string, path: string) { return ["workspaces", workspaceId, "files", "read", path] as const; } async function fetchFileRead( workspaceId: string, path: string, ): Promise { const url = `/api/workspaces/${encodeURIComponent(workspaceId)}/files/read?` + new URLSearchParams({ path }).toString(); const res = await apiClient(url); if (!res.ok) { throw new Error(`Failed to read file: ${res.status}`); } return (await res.json()) as ReadFileResponse; } export function useFileRead( workspaceId: string | null, path: string | null, ): UseQueryResult { return useQuery({ queryKey: fileReadQueryKey(workspaceId ?? "", path ?? ""), queryFn: () => fetchFileRead(workspaceId!, path!), enabled: !!workspaceId && !!path, }); } export function useFileList( workspaceId: string | null, path: string, enabled = true, ): UseQueryResult { const queryPath = path || "."; return useQuery({ queryKey: fileListQueryKey(workspaceId ?? "", queryPath), queryFn: () => fetchFileList(workspaceId!, queryPath), enabled: !!workspaceId && enabled, refetchInterval: 5_000, refetchOnWindowFocus: true, }); } export function useFileWrite(): UseMutationResult< void, Error, { path: string; content: string; workspaceId: string } > { const queryClient = useQueryClient(); return useMutation({ mutationFn: async ({ workspaceId, path, content }) => { const url = `/api/workspaces/${encodeURIComponent(workspaceId)}/files/write`; const res = await apiClient(url, { method: "PUT", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ path, content }), }); if (!res.ok) { throw new Error(`Failed to save file: ${res.status}`); } }, onSuccess: (_, variables) => { queryClient.invalidateQueries({ queryKey: fileReadQueryKey(variables.workspaceId, variables.path), }); queryClient.invalidateQueries({ queryKey: ["workspaces", variables.workspaceId, "files"], }); }, }); } export interface MkdirVariables { workspaceId: string; path: string; } export function useFileMkdir(): UseMutationResult< void, Error, MkdirVariables > { const queryClient = useQueryClient(); return useMutation({ mutationFn: async ({ workspaceId, path }) => { const url = `/api/workspaces/${encodeURIComponent(workspaceId)}/files/mkdir`; const res = await apiClient(url, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ path }), }); if (!res.ok) { throw new Error(`Failed to create folder: ${res.status}`); } }, onSuccess: (_, variables) => { queryClient.invalidateQueries({ queryKey: ["workspaces", variables.workspaceId, "files"], }); }, }); } export interface RemoveVariables { workspaceId: string; path: string; } export function useFileRemove(): UseMutationResult< void, Error, RemoveVariables > { const queryClient = useQueryClient(); return useMutation({ mutationFn: async ({ workspaceId, path }) => { const url = `/api/workspaces/${encodeURIComponent(workspaceId)}/files?` + new URLSearchParams({ path }).toString(); const res = await apiClient(url, { method: "DELETE" }); if (!res.ok) { throw new Error(`Failed to delete file: ${res.status}`); } }, onSuccess: (_, variables) => { queryClient.invalidateQueries({ queryKey: ["workspaces", variables.workspaceId, "files"], }); queryClient.invalidateQueries({ queryKey: fileReadQueryKey(variables.workspaceId, variables.path), }); }, }); } export interface RenameVariables { workspaceId: string; oldPath: string; newPath: string; } export function useFileRename(): UseMutationResult< void, Error, RenameVariables > { const queryClient = useQueryClient(); return useMutation({ mutationFn: async ({ workspaceId, oldPath, newPath }) => { const url = `/api/workspaces/${encodeURIComponent(workspaceId)}/files/rename`; const res = await apiClient(url, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ oldPath, newPath }), }); if (!res.ok) { throw new Error(`Failed to rename file: ${res.status}`); } }, onSuccess: (_, variables) => { queryClient.invalidateQueries({ queryKey: ["workspaces", variables.workspaceId, "files"], }); queryClient.invalidateQueries({ queryKey: fileReadQueryKey(variables.workspaceId, variables.oldPath), }); }, }); }