"use client" import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query" import { apiClient } from "@/lib/api/client" import { driveFilesListApiPath, driveFilterCorpusApiPath } from "@/lib/api/drive-download" import { driveMountFilesApiPath, driveOrgFilesApiPath, type DrivePathRef, withMoveCopyRefs, withPathRefBody, } from "@/lib/api/drive-roots" import { useAuthReady } from "@/lib/api/use-auth-ready" import { useDemoDrive, useIsDemoDrive } from "@/lib/demo/demo-drive-context" import { DEMO_DRIVE_QUERY_ROOT } from "@/lib/demo/demo-drive-bootstrap" import { useDemoDriveStore } from "@/lib/demo/demo-drive-store" import type { DriveFileInfo, DriveListResponse, DriveMount, DriveOrgFolder, DriveQuota, DriveShare, ShareRecipientLookup, } from "@/lib/api/types" import type { DriveShareMode } from "@/lib/drive/drive-share-types" function filesKey(path: string, page: number, q: string) { return ["drive", "files", path, page, q] as const } function demoFilesKey(path: string, page: number, q: string, version: number) { return [...DEMO_DRIVE_QUERY_ROOT, "files", path, page, q, version] as const } function filterCorpusKey(path: string) { return ["drive", "filter-corpus", path] as const } export function useDriveFilterCorpus(path: string, enabled = true) { const { ready, authenticated } = useAuthReady() const apiPath = driveFilterCorpusApiPath(path) return useQuery({ queryKey: filterCorpusKey(path), enabled: ready && authenticated && enabled, staleTime: 60_000, queryFn: () => apiClient.get(apiPath), }) } export function useDriveList(path: string, page = 1, q = "", enabled = true) { const { ready, authenticated } = useAuthReady() const isDemoDrive = useIsDemoDrive() const demoVersion = useDemoDriveStore((s) => s.version) const apiPath = driveFilesListApiPath(path) return useQuery({ queryKey: isDemoDrive ? demoFilesKey(path, page, q, demoVersion) : filesKey(path, page, q), enabled: ready && authenticated && enabled, queryFn: () => { if (isDemoDrive) { return useDemoDriveStore.getState().listFiles(path, page) } return apiClient.get( `${apiPath}?page=${page}&page_size=50${q ? `&q=${encodeURIComponent(q)}` : ""}` ) }, initialData: isDemoDrive ? () => useDemoDriveStore.getState().listFiles(path, page) : undefined, }) } export function useDriveFileById(fileId: string, enabled = true) { const { ready, authenticated } = useAuthReady() return useQuery({ queryKey: ["drive", "file", fileId], enabled: ready && authenticated && enabled && Boolean(fileId), staleTime: 30_000, queryFn: () => apiClient.get(`/drive/files/id/${encodeURIComponent(fileId)}`), }) } export function useDriveSharedWithMe(page = 1, q = "", enabled = true) { const { ready, authenticated } = useAuthReady() const isDemoDrive = useIsDemoDrive() const demoVersion = useDemoDriveStore((s) => s.version) return useQuery({ queryKey: isDemoDrive ? [...DEMO_DRIVE_QUERY_ROOT, "shared", page, q, demoVersion] : ["drive", "shared", page, q], enabled: ready && authenticated && enabled, queryFn: () => { if (isDemoDrive) return useDemoDriveStore.getState().listShared() return apiClient.get( `/drive/shared?page=${page}&page_size=50${q ? `&q=${encodeURIComponent(q)}` : ""}` ) }, initialData: isDemoDrive ? () => useDemoDriveStore.getState().listShared() : undefined, }) } export function useDriveTrash(page = 1, q = "") { const { ready, authenticated } = useAuthReady() const isDemoDrive = useIsDemoDrive() const demoVersion = useDemoDriveStore((s) => s.version) return useQuery({ queryKey: isDemoDrive ? [...DEMO_DRIVE_QUERY_ROOT, "trash", page, q, demoVersion] : ["drive", "trash", page, q], enabled: ready && authenticated, queryFn: () => { if (isDemoDrive) return useDemoDriveStore.getState().listTrash() return apiClient.get( `/drive/trash?page=${page}&page_size=50${q ? `&q=${encodeURIComponent(q)}` : ""}` ) }, initialData: isDemoDrive ? () => useDemoDriveStore.getState().listTrash() : undefined, }) } export function useDriveRecent() { const { ready, authenticated } = useAuthReady() const isDemoDrive = useIsDemoDrive() const demoVersion = useDemoDriveStore((s) => s.version) return useQuery({ queryKey: isDemoDrive ? [...DEMO_DRIVE_QUERY_ROOT, "recent", demoVersion] : ["drive", "recent"], enabled: ready && authenticated, retry: 1, queryFn: () => { if (isDemoDrive) return useDemoDriveStore.getState().listRecent() return apiClient.get("/drive/recent?page_size=50") }, initialData: isDemoDrive ? () => useDemoDriveStore.getState().listRecent() : undefined, }) } export function useDriveStarred(path = "/") { const { ready, authenticated } = useAuthReady() const isDemoDrive = useIsDemoDrive() const demoVersion = useDemoDriveStore((s) => s.version) const suffix = path === "/" ? "" : path return useQuery({ queryKey: isDemoDrive ? [...DEMO_DRIVE_QUERY_ROOT, "starred", path, demoVersion] : ["drive", "starred", path], enabled: ready && authenticated, queryFn: () => { if (isDemoDrive) return useDemoDriveStore.getState().listStarred(path) return apiClient.get(`/drive/starred${suffix}?page_size=50`) }, initialData: isDemoDrive ? () => useDemoDriveStore.getState().listStarred(path) : undefined, }) } function searchKey(q: string, scope: string, path: string, page: number, suggest: boolean) { return ["drive", "search", q, scope, path, page, suggest] as const } export function useDriveSearch( q: string, scope: string, path: string, page = 1, enabled = true ) { const { ready, authenticated } = useAuthReady() const isDemoDrive = useIsDemoDrive() const demoVersion = useDemoDriveStore((s) => s.version) const trimmed = q.trim() return useQuery({ queryKey: isDemoDrive ? [...DEMO_DRIVE_QUERY_ROOT, "search", trimmed, scope, path, page, false, demoVersion] : searchKey(trimmed, scope, path, page, false), enabled: ready && authenticated && enabled && trimmed.length > 0, queryFn: () => { if (isDemoDrive) { return useDemoDriveStore.getState().search(trimmed, scope, path, page) } const params = new URLSearchParams({ q: trimmed, scope, page: String(page), page_size: "50", }) if (scope === "folder" && path !== "/") { params.set("path", path) } return apiClient.get(`/drive/search?${params.toString()}`) }, initialData: isDemoDrive && trimmed.length > 0 ? () => useDemoDriveStore.getState().search(trimmed, scope, path, page) : undefined, }) } export function useDriveSearchSuggestions( q: string, scope: string, path: string, enabled = true ) { const { ready, authenticated } = useAuthReady() const isDemoDrive = useIsDemoDrive() const demoVersion = useDemoDriveStore((s) => s.version) const trimmed = q.trim() return useQuery({ queryKey: isDemoDrive ? [...DEMO_DRIVE_QUERY_ROOT, "search", trimmed, scope, path, 1, true, demoVersion] : searchKey(trimmed, scope, path, 1, true), enabled: ready && authenticated && enabled && trimmed.length >= 2, staleTime: 30_000, queryFn: () => { if (isDemoDrive) { const res = useDemoDriveStore.getState().search(trimmed, scope, path, 1) return { ...res, files: res.files.slice(0, 8) } } const params = new URLSearchParams({ q: trimmed, scope, suggest: "1", page_size: "8", }) if (scope === "folder" && path !== "/") { params.set("path", path) } return apiClient.get(`/drive/search?${params.toString()}`) }, initialData: isDemoDrive && trimmed.length >= 2 ? () => { const res = useDemoDriveStore.getState().search(trimmed, scope, path, 1) return { ...res, files: res.files.slice(0, 8) } } : undefined, }) } export function useDriveQuota() { const { ready, authenticated } = useAuthReady() return useQuery({ queryKey: ["drive", "quota"], enabled: ready && authenticated, retry: 1, queryFn: () => apiClient.get("/drive/quota"), }) } export function useDriveShares(filePath: string, enabled: boolean, pathRef?: DrivePathRef) { const { ready, authenticated } = useAuthReady() const rootQs = pathRef?.root && pathRef.root !== "personal" ? `&root=${encodeURIComponent(pathRef.root)}&root_id=${encodeURIComponent(pathRef.root_id ?? "")}` : "" return useQuery({ queryKey: ["drive", "shares", filePath, pathRef?.root, pathRef?.root_id], enabled: ready && authenticated && enabled && Boolean(filePath), queryFn: () => apiClient.get<{ shares: DriveShare[] }>( `/drive/shares?path=${encodeURIComponent(filePath)}${rootQs}` ), }) } export function useDriveOrgFolders(enabled = true) { const { ready, authenticated } = useAuthReady() return useQuery({ queryKey: ["drive", "org-folders"], enabled: ready && authenticated && enabled, staleTime: 60_000, queryFn: async () => { const res = await apiClient.get<{ folders: DriveOrgFolder[] }>("/drive/org-folders") return res.folders ?? [] }, }) } export function useDriveOrgList(folderId: string, path: string, page = 1, enabled = true) { const { ready, authenticated } = useAuthReady() const apiPath = driveOrgFilesApiPath(folderId, path) return useQuery({ queryKey: ["drive", "org", folderId, path, page], enabled: ready && authenticated && enabled && Boolean(folderId), queryFn: () => apiClient.get(`${apiPath}?page=${page}&page_size=50`), }) } export function useDriveMounts(orgSlugs: string[] = [], enabled = true) { const { ready, authenticated } = useAuthReady() const slugParam = orgSlugs.filter(Boolean).join(",") return useQuery({ queryKey: ["drive", "mounts", slugParam], enabled: ready && authenticated && enabled, staleTime: 30_000, queryFn: async () => { const qs = slugParam ? `?org_slugs=${encodeURIComponent(slugParam)}` : "" const res = await apiClient.get<{ mounts: DriveMount[] }>(`/drive/mounts${qs}`) return res.mounts ?? [] }, }) } export function useDriveMountList(mountId: string, path: string, page = 1, enabled = true) { const { ready, authenticated } = useAuthReady() const apiPath = driveMountFilesApiPath(mountId, path) return useQuery({ queryKey: ["drive", "mount", mountId, path, page], enabled: ready && authenticated && enabled && Boolean(mountId), queryFn: () => apiClient.get(`${apiPath}?page=${page}&page_size=50`), }) } export function useDriveMutations(pathRef?: DrivePathRef) { const qc = useQueryClient() const demoDrive = useDemoDrive() const isDemoDrive = demoDrive?.enabled ?? false const invalidate = () => { if (isDemoDrive) { useDemoDriveStore.getState().bump() void qc.invalidateQueries({ queryKey: DEMO_DRIVE_QUERY_ROOT }) return } void qc.invalidateQueries({ queryKey: ["drive"] }) } const ref = pathRef ?? { path: "/" } const rootPrefix = ref.root === "org" && ref.root_id ? `/drive/org-folders/${encodeURIComponent(ref.root_id)}` : ref.root === "mount" && ref.root_id ? `/drive/mounts/${encodeURIComponent(ref.root_id)}` : "/drive" const createFolder = useMutation({ mutationFn: async (path: string) => { if (isDemoDrive) { useDemoDriveStore.getState().createFolder(path) return } if (ref.root === "org" || ref.root === "mount") { return apiClient.post(`${rootPrefix}/folders${path}`, {}) } return apiClient.post(`/drive/folders${path}`, {}) }, onSuccess: invalidate, }) const deleteFile = useMutation({ mutationFn: async (path: string) => { if (isDemoDrive) { useDemoDriveStore.getState().deleteFile(path) demoDrive?.notify("Élément placé dans la corbeille") return } if (ref.root === "org" || ref.root === "mount") { return apiClient.delete(`${rootPrefix}/files${path}`) } return apiClient.delete(`/drive/files${path}`) }, onSuccess: invalidate, }) const rename = useMutation({ mutationFn: async (body: { path: string; new_name: string }) => { if (isDemoDrive) { useDemoDriveStore.getState().rename(body.path, body.new_name) return } return apiClient.post( "/drive/rename", withPathRefBody(body, { ...ref, path: body.path }) ) }, onSuccess: invalidate, }) const move = useMutation({ mutationFn: (body: { source: string; destination: string; sourceRef?: DrivePathRef; destRef?: DrivePathRef }) => apiClient.post( "/drive/move", withMoveCopyRefs( body.sourceRef ?? { ...ref, path: body.source }, body.destRef ?? { ...ref, path: body.destination } ) ), onSuccess: invalidate, }) const copy = useMutation({ mutationFn: (body: { source: string; destination: string; sourceRef?: DrivePathRef; destRef?: DrivePathRef }) => apiClient.post( "/drive/copy", withMoveCopyRefs( body.sourceRef ?? { ...ref, path: body.source }, body.destRef ?? { ...ref, path: body.destination } ) ), onSuccess: invalidate, }) const favorite = useMutation({ mutationFn: async (body: { path: string; favorite: boolean }) => { if (isDemoDrive) { useDemoDriveStore.getState().setFavorite(body.path, body.favorite) return } return apiClient.post( "/drive/favorite", withPathRefBody(body, { ...ref, path: body.path }) ) }, onSuccess: invalidate, }) const restore = useMutation({ mutationFn: async (name: string) => { if (isDemoDrive) { useDemoDriveStore.getState().restoreTrash(name) return } return apiClient.post("/drive/trash/restore", { name }) }, onSuccess: invalidate, }) const deleteTrash = useMutation({ mutationFn: async (name: string) => { if (isDemoDrive) { useDemoDriveStore.getState().deleteTrash(name) return } return apiClient.post("/drive/trash/delete", { name }) }, onSuccess: invalidate, }) const emptyTrash = useMutation({ mutationFn: async () => { if (isDemoDrive) { useDemoDriveStore.getState().emptyTrash() return } return apiClient.delete("/drive/trash") }, onSuccess: invalidate, }) const createShare = useMutation({ mutationFn: (body: { path: string mode?: DriveShareMode role?: string permissions?: number share_type?: number share_with?: string note?: string send_mail?: boolean }) => apiClient.post("/drive/shares", { ...withPathRefBody({ path: body.path }, { ...ref, path: body.path }), mode: body.mode ?? "public", ...(body.mode === "contact" ? { share_with: body.share_with, note: body.note, send_mail: body.send_mail ?? true, } : body.mode === "internal" ? { share_type: 3 } : { share_type: body.share_type ?? 3 }), ...(body.permissions != null && body.permissions > 0 ? { permissions: body.permissions } : { role: body.role ?? "viewer" }), }), onSuccess: (_data, variables) => { qc.invalidateQueries({ queryKey: ["drive"] }) if (variables.path) { qc.invalidateQueries({ queryKey: ["drive", "shares", variables.path] }) } }, }) const deleteShare = useMutation({ mutationFn: (shareId: string) => apiClient.delete(`/drive/shares/${shareId}`), onSuccess: invalidate, }) const lookupShareRecipient = useMutation({ mutationFn: (email: string) => apiClient.get( `/drive/shares/recipients/lookup?email=${encodeURIComponent(email)}` ), }) const createFile = useMutation({ mutationFn: (body: { parent_path: string; name: string; kind: string }) => apiClient.post<{ path: string; file_id?: number }>("/drive/files/new", body), onSuccess: invalidate, }) return { createFolder, deleteFile, rename, move, copy, favorite, restore, deleteTrash, emptyTrash, createShare, deleteShare, lookupShareRecipient, createFile, invalidate } } export function useDriveMountMutations() { const qc = useQueryClient() const invalidate = () => qc.invalidateQueries({ queryKey: ["drive", "mounts"] }) const createMount = useMutation({ mutationFn: (body: { scope: "user" | "org" org_slug?: string display_name: string backend_type: string webdav?: { host: string; root: string; user: string; password: string; secure?: boolean } oauth_backend?: string oauth_auth?: string }) => apiClient.post("/drive/mounts", body), onSuccess: invalidate, }) const deleteMount = useMutation({ mutationFn: (mountId: string) => apiClient.delete(`/drive/mounts/${encodeURIComponent(mountId)}`), onSuccess: invalidate, }) const fetchOAuthURL = async (mountId: string, redirectUri: string) => apiClient.get<{ oauth_url: string }>( `/drive/mounts/${encodeURIComponent(mountId)}/oauth-url?redirect_uri=${encodeURIComponent(redirectUri)}` ) const completeOAuth = useMutation({ mutationFn: ({ mountId, code, redirectUri }: { mountId: string; code: string; redirectUri: string }) => apiClient.post<{ status: string }>(`/drive/mounts/${encodeURIComponent(mountId)}/oauth/complete`, { code, redirect_uri: redirectUri, }), onSuccess: invalidate, }) return { createMount, deleteMount, fetchOAuthURL, completeOAuth, invalidate } } /** @deprecated Use openDriveFileInNewTab / downloadDriveFile — API requires Authorization. */ export function fileDownloadUrl(path: string): string { const base = process.env.NEXT_PUBLIC_API_URL ?? "/api/v1" return `${base}/drive/download${path.startsWith("/") ? path : `/${path}`}` } export type { DriveFileInfo }