import { useCallback, useMemo } from 'react'; import { useSearchParams } from 'react-router-dom'; import { MACHINE_CATEGORIES, MACHINE_DIFFICULTIES } from '../modules/machines/schema'; import type { MachineCategory, MachineDifficulty, MachineRegistryEntry } from '../modules/machines/schema'; export type CatalogueSort = 'name' | 'newest' | 'complexity'; export interface CatalogueFilterState { query: string; category: MachineCategory | 'all'; difficulty: MachineDifficulty | 'all'; sort: CatalogueSort; } function parseCategory(value: string | null): MachineCategory | 'all' { return MACHINE_CATEGORIES.includes(value as MachineCategory) ? (value as MachineCategory) : 'all'; } function parseDifficulty(value: string | null): MachineDifficulty | 'all' { return MACHINE_DIFFICULTIES.includes(value as MachineDifficulty) ? (value as MachineDifficulty) : 'all'; } function parseSort(value: string | null): CatalogueSort { return value === 'newest' || value === 'complexity' ? value : 'name'; } function machineMatchesQuery(machine: MachineRegistryEntry, query: string): boolean { if (!query) { return true; } const haystack = [ machine.title, machine.summary, machine.description, machine.category, machine.difficulty, ...machine.tags, ...machine.parts.map((part) => part.name) ] .join(' ') .toLowerCase(); return haystack.includes(query.toLowerCase()); } function sortMachines(machines: MachineRegistryEntry[], sort: CatalogueSort): MachineRegistryEntry[] { return [...machines].sort((left, right) => { if (sort === 'newest') { return right.releasedAt.localeCompare(left.releasedAt); } if (sort === 'complexity') { return right.complexity - left.complexity || left.title.localeCompare(right.title); } return left.title.localeCompare(right.title); }); } export function useCatalogueFilters(machines: MachineRegistryEntry[]) { const [searchParams, setSearchParams] = useSearchParams(); const filters = useMemo( () => ({ query: searchParams.get('q') ?? '', category: parseCategory(searchParams.get('category')), difficulty: parseDifficulty(searchParams.get('difficulty')), sort: parseSort(searchParams.get('sort')) }), [searchParams] ); const updateParam = useCallback( (key: string, value: string | null) => { const nextParams = new URLSearchParams(searchParams); if (!value || value === 'all') { nextParams.delete(key); } else { nextParams.set(key, value); } setSearchParams(nextParams, { replace: true }); }, [searchParams, setSearchParams] ); const filteredMachines = useMemo(() => { const filtered = machines.filter((machine) => { const matchesQuery = machineMatchesQuery(machine, filters.query.trim()); const matchesCategory = filters.category === 'all' || machine.category === filters.category; const matchesDifficulty = filters.difficulty === 'all' || machine.difficulty === filters.difficulty; return matchesQuery && matchesCategory && matchesDifficulty; }); return sortMachines(filtered, filters.sort); }, [filters, machines]); return { filters, filteredMachines, resultCount: filteredMachines.length, setQuery: (value: string) => updateParam('q', value.trim()), setCategory: (value: MachineCategory | 'all') => updateParam('category', value), setDifficulty: (value: MachineDifficulty | 'all') => updateParam('difficulty', value), setSort: (value: CatalogueSort) => updateParam('sort', value) }; }