import { useState, useEffect, useCallback, useRef, useMemo, 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> } type DiffLoadCancellation = { current: boolean } function useDiffLoadCancellation() { const activeDiffLoadRef = useRef(null) const cancelActiveDiffLoad = useCallback(() => { if (!activeDiffLoadRef.current) return activeDiffLoadRef.current.current = true activeDiffLoadRef.current = null }, []) const createDiffLoadCancellation = useCallback(() => { cancelActiveDiffLoad() const cancellation = { current: false } activeDiffLoadRef.current = cancellation return cancellation }, [cancelActiveDiffLoad]) const clearDiffLoadCancellation = useCallback((cancellation: DiffLoadCancellation) => { if (activeDiffLoadRef.current === cancellation) { activeDiffLoadRef.current = null } }, []) return { cancelActiveDiffLoad, createDiffLoadCancellation, clearDiffLoadCancellation, } } 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: DiffLoadCancellation, { 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 closeDiffMode(activeTabPath: string | null, { setDiffMode, setDiffContent, setDiffLoading, setDiffPath, }: DiffStateSetters) { setDiffPath(activeTabPath) setDiffMode(false) setDiffContent(null) setDiffLoading(false) } 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]) } function useDiffPathReset( activeTabPath: string | null, cancelActiveDiffLoad: () => void, diffState: DiffStateSetters, ) { useEffect(() => { cancelActiveDiffLoad() closeDiffMode(activeTabPath, diffState) }, [activeTabPath, cancelActiveDiffLoad, diffState]) } function useCancelDiffLoadOnUnmount(cancelActiveDiffLoad: () => void) { useEffect(() => () => cancelActiveDiffLoad(), [cancelActiveDiffLoad]) } 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) const diffState = useMemo(() => ({ setDiffMode, setDiffContent, setDiffLoading, setDiffPath, }), [setDiffMode, setDiffContent, setDiffLoading, setDiffPath]) const { cancelActiveDiffLoad, createDiffLoadCancellation, clearDiffLoadCancellation, } = useDiffLoadCancellation() useDiffPathReset(activeTabPath, cancelActiveDiffLoad, diffState) useCancelDiffLoadOnUnmount(cancelActiveDiffLoad) usePendingCommitDiffRequest({ activeTabPath, onLoadDiff, onLoadDiffAtCommit, pendingCommitDiffRequest, onPendingCommitDiffHandled, setDiffMode, setDiffContent, setDiffLoading, setDiffPath, }) const isDiffVisible = diffMode && diffPath === activeTabPath const handleToggleDiff = useCallback(async () => { if (isDiffVisible) { cancelActiveDiffLoad() closeDiffMode(activeTabPath, diffState) return } if (!activeTabPath || !onLoadDiff) return const cancellation = createDiffLoadCancellation() try { await loadDiffForPath(activeTabPath, onLoadDiff, buildGuardedDiffStateSetters(cancellation, diffState)) } finally { clearDiffLoadCancellation(cancellation) } }, [activeTabPath, cancelActiveDiffLoad, clearDiffLoadCancellation, createDiffLoadCancellation, diffState, isDiffVisible, onLoadDiff]) const handleViewCommitDiff = useCallback(async (commitHash: string) => { if (!activeTabPath) return const cancellation = createDiffLoadCancellation() try { await loadCommitDiffForPath(activeTabPath, commitHash, onLoadDiffAtCommit, buildGuardedDiffStateSetters(cancellation, diffState)) } finally { clearDiffLoadCancellation(cancellation) } }, [activeTabPath, clearDiffLoadCancellation, createDiffLoadCancellation, diffState, onLoadDiffAtCommit]) return { diffMode: isDiffVisible, diffContent: isDiffVisible ? diffContent : null, diffLoading, handleToggleDiff, handleViewCommitDiff, } }