"use client" import { useCallback, useEffect, useMemo, useState, type ReactNode } from "react" import { useRouter } from "next/navigation" import type { Editor } from "@tiptap/react" import { toast } from "sonner" import { DocsDetailsDialog } from "@/components/drive/richtext/docs-details-dialog" import { DocsOpenDialog } from "@/components/drive/richtext/docs-open-dialog" import { DocsPageSetupDialog } from "@/components/drive/richtext/docs-page-setup-dialog" import type { DocsFileMenuActions } from "@/components/drive/richtext/docs-file-menu" import { DriveMoveDialog } from "@/components/drive/drive-move-dialog" import { DriveNameDialog } from "@/components/drive/drive-name-dialog" import { apiClient } from "@/lib/api/client" import { useDriveList, useDriveMutations } from "@/lib/api/hooks/use-drive-queries" import type { DriveFileInfo } from "@/lib/api/types" import { displayFileName } from "@/lib/drive/display-file-name" import { nextUntitledName } from "@/lib/drive/drive-default-name" import { buildMoveDestination, parentFolderPath } from "@/lib/drive/drive-move-items" import { buildDriveDocsEditHref } from "@/lib/drive/drive-url" import type { DocsExportSnapshot } from "@/lib/drive/docs-export-snapshot" import { exportDocsContent, type DocsDownloadFormat, } from "@/lib/drive/docs-file-menu-export" import { printDocsDocument } from "@/lib/drive/docs-print" import { openRichTextDocument } from "@/lib/drive/open-rich-text-document" import type { PageFormatId } from "@/lib/drive/page-formats" import type { DocPageSetup } from "@/lib/drive/doc-page-setup" import { useDocsKeyboardShortcutsStore } from "@/lib/stores/docs-keyboard-shortcuts-store" function buildCopyFileName(originalName: string, siblingNames: string[]): string { const name = displayFileName(originalName) const dot = name.lastIndexOf(".") const base = dot > 0 ? name.slice(0, dot) : name const ext = dot > 0 ? name.slice(dot) : "" const siblings = siblingNames.map(displayFileName) let candidate = `Copie de ${base}${ext}` if (!siblings.includes(candidate)) return candidate let n = 2 while (siblings.includes(`Copie de ${base} (${n})${ext}`)) { n += 1 } return `Copie de ${base} (${n})${ext}` } export function useDocsFileMenu({ file, editor, pageSetup, fallbackFormatId, getExportSnapshot, onPageSetupApply, onShareClick, onRenameRequest, onFileMoved, onPurgeSidecarAndReimport, disabled, }: { file?: DriveFileInfo editor: Editor | null pageSetup: DocPageSetup | null fallbackFormatId: PageFormatId getExportSnapshot: () => DocsExportSnapshot | null onPageSetupApply: (setup: DocPageSetup) => void onShareClick?: () => void onRenameRequest?: () => void onFileMoved?: (newPath: string) => void onPurgeSidecarAndReimport?: () => void disabled?: boolean }) { const router = useRouter() const mutations = useDriveMutations() const parentPath = file ? parentFolderPath(file.path) : "/" const siblingList = useDriveList(parentPath, 1, "", false) const siblingNames = useMemo( () => (siblingList.data?.files ?? []).map((item) => item.name), [siblingList.data?.files] ) const [openDialogOpen, setOpenDialogOpen] = useState(false) const [moveDialogOpen, setMoveDialogOpen] = useState(false) const [newDocDialogOpen, setNewDocDialogOpen] = useState(false) const [pageSetupOpen, setPageSetupOpen] = useState(false) const [detailsOpen, setDetailsOpen] = useState(false) const [newDocDefaultName, setNewDocDefaultName] = useState("") const navigateToFile = useCallback( async (path: string) => { const info = await apiClient.get( `/drive/files/info${path.startsWith("/") ? path : `/${path}`}` ) if (!info.file_id) { throw new Error("Identifiant du document introuvable") } router.push(buildDriveDocsEditHref(info.file_id)) }, [router] ) const createDocument = useCallback( async (rawName: string) => { const name = rawName.trim().replace(/\//g, "") if (!name) return const fileName = name.endsWith(".docx") ? name : `${name}.docx` try { const { path } = await mutations.createFile.mutateAsync({ parent_path: parentPath, name: fileName, kind: "document", }) toast.success("Document créé") await navigateToFile(path) } catch { toast.error("Impossible de créer le document") throw new Error("create failed") } }, [mutations.createFile, navigateToFile, parentPath] ) const makeCopy = useCallback(async () => { if (!file) return const copyName = buildCopyFileName(file.name, siblingNames) const destination = buildMoveDestination(parentPath, copyName) try { await mutations.copy.mutateAsync({ source: file.path, destination }) toast.success("Copie créée") await navigateToFile(destination) } catch { toast.error("Impossible de créer la copie") } }, [file, mutations.copy, navigateToFile, parentPath, siblingNames]) const moveToTrash = useCallback(async () => { if (!file) return try { await mutations.deleteFile.mutateAsync(file.path) toast.success("Document déplacé dans la corbeille") router.push("/drive") } catch { toast.error("Impossible de déplacer dans la corbeille") } }, [file, mutations.deleteFile, router]) const downloadFormat = useCallback( async (format: DocsDownloadFormat) => { if (!file) return if (format === "html-zip" || format === "odt" || format === "rtf" || format === "epub") { toast.info("Export bientôt disponible pour ce format") return } const snapshot = getExportSnapshot() const loadingToast = format === "pdf" ? toast.loading("Génération du PDF…") : undefined try { const result = await exportDocsContent(format, snapshot, editor, file.name) if (result === "unsupported") { toast.error("Export indisponible") } else if (format === "pdf") { toast.success("PDF téléchargé") } } catch { toast.error("Échec de l'export") } finally { if (loadingToast != null) toast.dismiss(loadingToast) } }, [editor, file, getExportSnapshot] ) const handlePrint = useCallback(async () => { const snapshot = getExportSnapshot() if (!snapshot) { toast.error("Impossible d'imprimer le document") return } try { await printDocsDocument(snapshot) } catch (error) { console.error("[docs] print failed", error) toast.error("Impossible d'imprimer le document") } }, [getExportSnapshot]) const soon = useCallback((label: string) => { toast.info(`${label} — bientôt disponible`) }, []) const menuDisabled = disabled || !file const actions = useMemo( () => ({ onNewDocument: () => { setNewDocDefaultName(nextUntitledName(siblingNames, "Document", ".docx")) setNewDocDialogOpen(true) }, onNewFromTemplate: () => soon("Galerie de modèles"), onOpen: () => setOpenDialogOpen(true), onMakeCopy: () => void makeCopy(), onShareWithUsers: () => onShareClick?.(), onPublishToWeb: () => onShareClick?.(), onEmailFile: () => soon("Envoi par e-mail"), onEmailCollaborators: () => soon("Envoi aux collaborateurs"), onEmailDraft: () => soon("Brouillon d'e-mail"), onDownload: (format) => void downloadFormat(format), onRename: () => onRenameRequest?.(), onMove: () => setMoveDialogOpen(true), onAddShortcut: () => soon("Raccourci Drive"), onMoveToTrash: () => void moveToTrash(), onNameCurrentVersion: () => soon("Nommer la version actuelle"), onShowVersionHistory: () => soon("Historique des versions"), onToggleOffline: () => soon("Disponible hors connexion"), onDetails: () => setDetailsOpen(true), onSecurityLimits: () => soon("Limites de sécurité"), onPageSetup: () => setPageSetupOpen(true), onPrint: () => void handlePrint(), ...(onPurgeSidecarAndReimport ? { onPurgeSidecarAndReimport: () => void onPurgeSidecarAndReimport() } : {}), }), [ downloadFormat, handlePrint, makeCopy, moveToTrash, onPurgeSidecarAndReimport, onRenameRequest, onShareClick, siblingNames, soon, ] ) useEffect(() => { if (menuDisabled) return const onKeyDown = (event: KeyboardEvent) => { const id = useDocsKeyboardShortcutsStore.getState().matchEvent( event, (definition) => definition.scope === "document" && definition.handler === "custom" ) if (id === "file.open") { event.preventDefault() setOpenDialogOpen(true) return } if (id === "file.print") { event.preventDefault() void handlePrint() } } window.addEventListener("keydown", onKeyDown) return () => window.removeEventListener("keydown", onKeyDown) }, [handlePrint, menuDisabled]) const dialogs: ReactNode = file ? ( <> openRichTextDocument(target, { push: (href) => router.push(href), }) } /> { if (!destinationFolder) return onFileMoved?.(buildMoveDestination(destinationFolder, file.name)) }} /> ) : null return { actions, dialogs, disabled: menuDisabled, } }