"use client"; import { memo, useCallback } from "react"; import { IconGitCommit, IconGitPullRequest, IconGitMerge, IconGitCherryPick, IconLoader2, IconChevronDown, IconCloudDownload, IconCloudUpload, IconAlertTriangle, } from "@tabler/icons-react"; import { Button } from "@kandev/ui/button"; import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger, DropdownMenuSeparator, DropdownMenuSub, DropdownMenuSubTrigger, DropdownMenuSubContent, } from "@kandev/ui/dropdown-menu"; import { Tooltip, TooltipContent, TooltipTrigger } from "@kandev/ui/tooltip"; import { useSessionGit } from "@/hooks/domains/session/use-session-git"; import { useGitWithFeedback } from "@/hooks/use-git-with-feedback"; import { useVcsDialogs } from "@/components/vcs/vcs-dialogs"; import { useActiveTaskPR } from "@/hooks/domains/github/use-task-pr"; import type { ReactNode } from "react"; function determinePrimaryAction( uncommittedFileCount: number, aheadCount: number, behindCount: number, hasOpenPR: boolean, ): "commit" | "push" | "pr" | "rebase" { if (uncommittedFileCount > 0) return "commit"; if (aheadCount > 0 && hasOpenPR) return "push"; if (aheadCount > 0) return "pr"; if (behindCount > 0) return "rebase"; return "commit"; } type PrimaryButtonConfig = { icon: ReactNode; label: string; badge: number | null; tooltip: string; onClick: () => void; }; function buildCommitConfig( uncommittedFileCount: number, openCommitDialog: () => void, ): PrimaryButtonConfig { return { icon: , label: "Commit", badge: uncommittedFileCount > 0 ? uncommittedFileCount : null, tooltip: uncommittedFileCount > 0 ? `Commit ${uncommittedFileCount} changed file${uncommittedFileCount !== 1 ? "s" : ""}` : "No changes to commit", onClick: openCommitDialog, }; } function buildPrConfig(aheadCount: number, openPRDialog: () => void): PrimaryButtonConfig { return { icon: , label: "Create PR", badge: aheadCount > 0 ? aheadCount : null, tooltip: `Create PR (${aheadCount} commit${aheadCount !== 1 ? "s" : ""} ahead)`, onClick: openPRDialog, }; } function buildPushConfig(aheadCount: number, handlePush: () => void): PrimaryButtonConfig { return { icon: , label: "Push", badge: aheadCount > 0 ? aheadCount : null, tooltip: `Push ${aheadCount} commit${aheadCount !== 1 ? "s" : ""} to remote`, onClick: handlePush, }; } function buildRebaseConfig( behindCount: number, baseBranch: string | undefined, handleRebase: () => void, ): PrimaryButtonConfig { return { icon: , label: "Rebase", badge: behindCount > 0 ? behindCount : null, tooltip: `Rebase onto ${baseBranch || "origin/main"} (${behindCount} behind)`, onClick: handleRebase, }; } type PrimaryConfigArgs = { primaryAction: "commit" | "push" | "pr" | "rebase"; uncommittedFileCount: number; aheadCount: number; behindCount: number; baseBranch: string | undefined; openCommitDialog: () => void; openPRDialog: () => void; handlePush: () => void; handleRebase: () => void; }; function buildPrimaryButtonConfig({ primaryAction, uncommittedFileCount, aheadCount, behindCount, baseBranch, openCommitDialog, openPRDialog, handlePush, handleRebase, }: PrimaryConfigArgs): PrimaryButtonConfig { if (primaryAction === "push") return buildPushConfig(aheadCount, handlePush); if (primaryAction === "pr") return buildPrConfig(aheadCount, openPRDialog); if (primaryAction === "rebase") return buildRebaseConfig(behindCount, baseBranch, handleRebase); return buildCommitConfig(uncommittedFileCount, openCommitDialog); } type VcsDropdownItemsProps = { disabled: boolean; baseBranch?: string; hasMatchingUpstream: boolean | "" | null | undefined; behindCount: number; aheadCount: number; onPR: () => void; onPull: () => void; onPush: (force: boolean) => void; onRebase: () => void; onMerge: () => void; }; function VcsDropdownItems({ disabled, baseBranch, hasMatchingUpstream, behindCount, aheadCount, onPR, onPull, onPush, onRebase, onMerge, }: VcsDropdownItemsProps) { return ( Create PR Pull {hasMatchingUpstream && behindCount > 0 && ( ↓{behindCount} )} Push {hasMatchingUpstream && aheadCount > 0 && ( ↑{aheadCount} )} onPush(false)} disabled={disabled} > Push onPush(true)} disabled={disabled} > Force Push Rebase onto {baseBranch || "origin/main"} Merge from {baseBranch || "origin/main"} ); } type VcsSplitButtonProps = { sessionId: string | null; baseBranch?: string; }; function useGitActions(git: ReturnType, baseBranch?: string) { const gitWithFeedback = useGitWithFeedback(); const handlePull = useCallback(() => { gitWithFeedback(() => git.pull(), "Pull"); }, [gitWithFeedback, git]); const handlePush = useCallback( (force = false) => { gitWithFeedback(() => git.push({ force }), force ? "Force Push" : "Push"); }, [gitWithFeedback, git], ); const handleRebase = useCallback(() => { const targetBranch = baseBranch?.replace(/^origin\//, "") || "main"; gitWithFeedback(() => git.rebase(targetBranch), "Rebase"); }, [gitWithFeedback, git, baseBranch]); const handleMerge = useCallback(() => { const targetBranch = baseBranch?.replace(/^origin\//, "") || "main"; gitWithFeedback(() => git.merge(targetBranch), "Merge"); }, [gitWithFeedback, git, baseBranch]); return { handlePull, handlePush, handleRebase, handleMerge }; } const VcsSplitButton = memo(function VcsSplitButton({ sessionId, baseBranch, }: VcsSplitButtonProps) { const git = useSessionGit(sessionId); const { openCommitDialog, openPRDialog } = useVcsDialogs(); const activePR = useActiveTaskPR(); const hasOpenPR = activePR?.state === "open"; const { handlePull, handlePush, handleRebase, handleMerge } = useGitActions(git, baseBranch); const currentBranch = git.branch; const remoteBranch = git.remoteBranch; const hasMatchingUpstream = remoteBranch && currentBranch && remoteBranch === `origin/${currentBranch}`; const uncommittedFileCount = git.allFiles.length; const aheadCount = git.ahead; const behindCount = git.behind; const isDisabled = git.isLoading || !sessionId; const isGitLoading = git.isLoading; const primaryAction = determinePrimaryAction( uncommittedFileCount, aheadCount, behindCount, hasOpenPR, ); const primaryButtonConfig = buildPrimaryButtonConfig({ primaryAction, uncommittedFileCount, aheadCount, behindCount, baseBranch, openCommitDialog, openPRDialog, handlePush: () => handlePush(false), handleRebase, }); return (
{primaryButtonConfig.tooltip}
); }); export { VcsSplitButton };