import React, { createContext, useContext, useReducer, type ReactNode } from "react"; import type { AppState, QuickFilter, UnifiedAppState, UnifiedAppAction, } from "../types/index.ts"; import { filterProjects, sortProjects } from "../utils/project-utils.ts"; import { filterByViewMode, filterUnifiedRepos, filterUnifiedReposByQuickFilter, getUnifiedRepoLanguage, sortUnifiedRepos, } from "../github/unified.ts"; import { appReducer, initialState } from "./reducer.ts"; /** * Store context */ interface StoreContextValue { state: UnifiedAppState; dispatch: React.Dispatch; } export const StoreContext = createContext(null); /** * Store provider component */ export function StoreProvider({ children }: { children: ReactNode }) { const [state, dispatch] = useReducer(appReducer, initialState); return ( {children} ); } /** * Test store provider - accepts initial state and optional mock dispatch * Used for testing components without mock.module() */ export function TestStoreProvider({ children, initialState: testInitialState, dispatch: mockDispatch, }: { children: ReactNode; initialState: UnifiedAppState; dispatch?: React.Dispatch; }) { const [state, realDispatch] = useReducer(appReducer, testInitialState); const dispatch = mockDispatch ?? realDispatch; return ( {children} ); } /** * Hook to access the store */ export function useStore(): StoreContextValue { const context = useContext(StoreContext); if (!context) { throw new Error("useStore must be used within a StoreProvider"); } return context; } /** * Apply quick filter to projects */ function applyQuickFilter(projects: AppState["projects"], quickFilter: QuickFilter) { if (quickFilter === "all") return projects; return projects.filter((p) => { switch (quickFilter) { case "dirty": return p.status?.isDirty; case "unpushed": return p.status?.isAhead && p.status?.unpushedCommits > 0; case "no-remote": return p.type === "git" && !p.status?.hasRemote; default: return true; } }); } /** * Hook to get filtered and sorted projects */ export function useFilteredProjects() { const { state } = useStore(); const { projects, filterText, quickFilter, sortBy, sortDirection } = state; let result = projects; // Apply quick filter first result = applyQuickFilter(result, quickFilter); // Apply text filter if (filterText) { result = filterProjects(result, filterText); } // Apply sort result = sortProjects(result, sortBy, sortDirection); return result; } /** * Hook to get selected projects */ export function useSelectedProjects() { const { state } = useStore(); const filteredProjects = useFilteredProjects(); const { selectedIndices, cursorIndex } = state; // If nothing selected, return the project at cursor if (selectedIndices.size === 0) { const current = filteredProjects[cursorIndex]; return current ? [current] : []; } // Return selected projects (filter out undefined and properly type) return [...selectedIndices] .map((i) => filteredProjects[i]) .filter((p): p is NonNullable => p !== undefined); } /** * Hook to get filtered and sorted unified repos */ export function useFilteredUnifiedRepos() { const { state } = useStore(); const { unifiedRepos, viewMode, filterText, quickFilter, sortBy, sortDirection, languageFilter } = state; let result = unifiedRepos; // Apply view mode filter first result = filterByViewMode(result, viewMode); // Apply quick filter result = filterUnifiedReposByQuickFilter(result, quickFilter); // Apply text filter if (filterText) { result = filterUnifiedRepos(result, filterText); } // Apply language filter if (languageFilter) { result = result.filter((repo) => getUnifiedRepoLanguage(repo)?.toLowerCase() === languageFilter.toLowerCase() ); } // Apply sort result = sortUnifiedRepos(result, sortBy, sortDirection); return result; } /** * Hook to get selected unified repos */ export function useSelectedUnifiedRepos() { const { state } = useStore(); const filteredRepos = useFilteredUnifiedRepos(); const { selectedIndices, cursorIndex } = state; // If nothing selected, return the repo at cursor if (selectedIndices.size === 0) { const current = filteredRepos[cursorIndex]; return current ? [current] : []; } // Return selected repos (filter out undefined and properly type) return [...selectedIndices] .map((i) => filteredRepos[i]) .filter((r): r is NonNullable => r !== undefined); }