import { useState, useEffect, useCallback, type Dispatch, type SetStateAction, } from 'react' export interface CommitDiffRequest { requestId: number path: string commitHash?: string | null } interface UseDiffModeParams { activeTabPath: string | null onLoadDiff?: (path: string) => Promise onLoadDiffAtCommit?: (path: string, commitHash: string) => Promise pendingCommitDiffRequest?: CommitDiffRequest | null onPendingCommitDiffHandled?: (requestId: number) => void } interface DiffStateSetters { setDiffMode: Dispatch> setDiffContent: Dispatch> setDiffLoading: Dispatch> setDiffPath: Dispatch> } async function loadDiffForPath( path: string, onLoadDiff: ((path: string) => Promise) | undefined, { setDiffMode, setDiffContent, setDiffLoading, setDiffPath }: DiffStateSetters, ) { if (!onLoadDiff) return setDiffLoading(true) try { const diff = await onLoadDiff(path) setDiffContent(diff) setDiffPath(path) setDiffMode(true) } catch (err) { console.warn('Failed to load diff:', err) } finally { setDiffLoading(false) } } async function loadCommitDiffForPath( path: string, commitHash: string, onLoadDiffAtCommit: ((path: string, commitHash: string) => Promise) | undefined, { setDiffMode, setDiffContent, setDiffLoading, setDiffPath }: DiffStateSetters, ) { if (!onLoadDiffAtCommit) return setDiffLoading(true) try { const diff = await onLoadDiffAtCommit(path, commitHash) setDiffContent(diff) setDiffPath(path) setDiffMode(true) } catch (err) { console.warn('Failed to load commit diff:', err) } finally { setDiffLoading(false) } } function shouldHandlePendingCommitDiffRequest( activeTabPath: string | null, pendingCommitDiffRequest: CommitDiffRequest | null | undefined, ): pendingCommitDiffRequest is CommitDiffRequest { return !!pendingCommitDiffRequest && pendingCommitDiffRequest.path === activeTabPath } function hasCommitHash(pendingCommitDiffRequest: CommitDiffRequest): pendingCommitDiffRequest is CommitDiffRequest & { commitHash: string } { return typeof pendingCommitDiffRequest.commitHash === 'string' && pendingCommitDiffRequest.commitHash.length > 0 } function buildGuardedDiffStateSetters( cancelledRef: { current: boolean }, { setDiffMode, setDiffContent, setDiffLoading, setDiffPath }: DiffStateSetters, ): DiffStateSetters { return { setDiffMode: (value) => { if (!cancelledRef.current) setDiffMode(value) }, setDiffContent: (value) => { if (!cancelledRef.current) setDiffContent(value) }, setDiffLoading: (value) => { if (!cancelledRef.current) setDiffLoading(value) }, setDiffPath: (value) => { if (!cancelledRef.current) setDiffPath(value) }, } } function runPendingCommitDiffRequest( pendingCommitDiffRequest: CommitDiffRequest, onLoadDiff: ((path: string) => Promise) | undefined, onLoadDiffAtCommit: ((path: string, commitHash: string) => Promise) | undefined, onPendingCommitDiffHandled: ((requestId: number) => void) | undefined, diffState: DiffStateSetters, ) { const cancelledRef = { current: false } const loadDiffPromise = hasCommitHash(pendingCommitDiffRequest) ? loadCommitDiffForPath( pendingCommitDiffRequest.path, pendingCommitDiffRequest.commitHash, onLoadDiffAtCommit, buildGuardedDiffStateSetters(cancelledRef, diffState), ) : loadDiffForPath( pendingCommitDiffRequest.path, onLoadDiff, buildGuardedDiffStateSetters(cancelledRef, diffState), ) void loadDiffPromise.finally(() => { if (cancelledRef.current) return onPendingCommitDiffHandled?.(pendingCommitDiffRequest.requestId) }) return () => { cancelledRef.current = true } } function usePendingCommitDiffRequest({ activeTabPath, onLoadDiff, onLoadDiffAtCommit, pendingCommitDiffRequest, onPendingCommitDiffHandled, setDiffMode, setDiffContent, setDiffLoading, setDiffPath, }: UseDiffModeParams & DiffStateSetters) { useEffect(() => { if (!shouldHandlePendingCommitDiffRequest(activeTabPath, pendingCommitDiffRequest)) return if (hasCommitHash(pendingCommitDiffRequest) && !onLoadDiffAtCommit) { onPendingCommitDiffHandled?.(pendingCommitDiffRequest.requestId) return } if (!hasCommitHash(pendingCommitDiffRequest) && !onLoadDiff) { onPendingCommitDiffHandled?.(pendingCommitDiffRequest.requestId) return } return runPendingCommitDiffRequest( pendingCommitDiffRequest, onLoadDiff, onLoadDiffAtCommit, onPendingCommitDiffHandled, { setDiffMode, setDiffContent, setDiffLoading, setDiffPath }, ) }, [activeTabPath, onLoadDiff, onLoadDiffAtCommit, onPendingCommitDiffHandled, pendingCommitDiffRequest, setDiffContent, setDiffLoading, setDiffMode, setDiffPath]) } export function useDiffMode({ activeTabPath, onLoadDiff, onLoadDiffAtCommit, pendingCommitDiffRequest, onPendingCommitDiffHandled, }: UseDiffModeParams) { const [diffMode, setDiffMode] = useState(false) const [diffContent, setDiffContent] = useState(null) const [diffLoading, setDiffLoading] = useState(false) const [diffPath, setDiffPath] = useState(activeTabPath) usePendingCommitDiffRequest({ activeTabPath, onLoadDiff, onLoadDiffAtCommit, pendingCommitDiffRequest, onPendingCommitDiffHandled, setDiffMode, setDiffContent, setDiffLoading, setDiffPath, }) const isDiffVisible = diffMode && diffPath === activeTabPath const handleToggleDiff = useCallback(async () => { if (isDiffVisible) { setDiffPath(activeTabPath) setDiffMode(false) setDiffContent(null) return } if (!activeTabPath || !onLoadDiff) return await loadDiffForPath(activeTabPath, onLoadDiff, { setDiffMode, setDiffContent, setDiffLoading, setDiffPath, }) }, [activeTabPath, isDiffVisible, onLoadDiff, setDiffContent, setDiffLoading, setDiffMode, setDiffPath]) const handleViewCommitDiff = useCallback(async (commitHash: string) => { if (!activeTabPath) return await loadCommitDiffForPath(activeTabPath, commitHash, onLoadDiffAtCommit, { setDiffMode, setDiffContent, setDiffLoading, setDiffPath, }) }, [activeTabPath, onLoadDiffAtCommit, setDiffContent, setDiffLoading, setDiffMode, setDiffPath]) return { diffMode: isDiffVisible, diffContent: isDiffVisible ? diffContent : null, diffLoading, handleToggleDiff, handleViewCommitDiff, } }