import { useState, useCallback } from 'react'; import type { FeatureMeta, FeatureFilters, Property, HexagonPropertiesResponse, HexagonStatsResponse, } from '../types'; import { buildFilterString, apiUrl, logNonAbortError, authHeaders } from '../lib/api'; interface SelectedHexagon { id: string; type: 'hexagon' | 'postcode'; resolution: number; } interface UseHexagonSelectionOptions { filters: FeatureFilters; features: FeatureMeta[]; resolution: number; } export function useHexagonSelection({ filters, features, resolution }: UseHexagonSelectionOptions) { const [selectedHexagon, setSelectedHexagon] = useState(null); const [properties, setProperties] = useState([]); const [propertiesTotal, setPropertiesTotal] = useState(0); const [propertiesOffset, setPropertiesOffset] = useState(0); const [loadingProperties, setLoadingProperties] = useState(false); const [areaStats, setAreaStats] = useState(null); const [loadingAreaStats, setLoadingAreaStats] = useState(false); const [hoveredHexagon, setHoveredHexagon] = useState(null); const [rightPaneTab, setRightPaneTab] = useState<'pois' | 'properties' | 'area'>('pois'); const fetchHexagonStats = useCallback( async (h3: string, res: number, signal?: AbortSignal, fields?: string[]) => { const params = new URLSearchParams({ h3, resolution: res.toString(), }); const filterStr = buildFilterString(filters, features); if (filterStr) params.append('filters', filterStr); if (fields) { params.set('fields', fields.join(',')); } const response = await fetch(apiUrl('hexagon-stats', params), authHeaders({ signal })); return (await response.json()) as HexagonStatsResponse; }, [filters, features] ); const fetchPostcodeStats = useCallback( async (postcode: string, signal?: AbortSignal) => { const params = new URLSearchParams({ postcode }); const filterStr = buildFilterString(filters, features); if (filterStr) params.append('filters', filterStr); const response = await fetch(apiUrl('postcode-stats', params), authHeaders({ signal })); return (await response.json()) as HexagonStatsResponse; }, [filters, features] ); const fetchHexagonProperties = useCallback( async (h3: string, res: number, offset = 0) => { setLoadingProperties(true); try { const params = new URLSearchParams({ h3, resolution: res.toString(), limit: '100', offset: offset.toString(), }); const filterStr = buildFilterString(filters, features); if (filterStr) params.append('filters', filterStr); const response = await fetch(apiUrl('hexagon-properties', params), authHeaders()); const data: HexagonPropertiesResponse = await response.json(); if (offset === 0) { setProperties(data.properties); } else { setProperties((prev) => [...prev, ...data.properties]); } setPropertiesTotal(data.total); setPropertiesOffset(offset + data.properties.length); } catch (err) { console.error('Failed to fetch properties:', err); } finally { setLoadingProperties(false); } }, [filters, features] ); const handleHexagonClick = useCallback( (id: string, isPostcode = false) => { if (selectedHexagon?.id === id) { setSelectedHexagon(null); setProperties([]); setAreaStats(null); } else { const type = isPostcode ? 'postcode' : 'hexagon'; setSelectedHexagon({ id, type, resolution }); setProperties([]); setPropertiesTotal(0); setPropertiesOffset(0); setRightPaneTab('area'); if (isPostcode) { setLoadingAreaStats(true); fetchPostcodeStats(id) .then((stats) => setAreaStats(stats)) .catch((error) => logNonAbortError('Failed to fetch postcode stats', error)) .finally(() => setLoadingAreaStats(false)); } else { setLoadingAreaStats(true); fetchHexagonStats(id, resolution) .then((stats) => setAreaStats(stats)) .catch((error) => logNonAbortError('Failed to fetch area stats', error)) .finally(() => setLoadingAreaStats(false)); } } }, [selectedHexagon, resolution, fetchHexagonStats, fetchPostcodeStats] ); const handleHexagonHover = useCallback((h3: string | null) => { setHoveredHexagon(h3); }, []); const handleViewPropertiesFromArea = useCallback(() => { if (selectedHexagon && selectedHexagon.type === 'hexagon') { setRightPaneTab('properties'); setPropertiesOffset(0); fetchHexagonProperties(selectedHexagon.id, selectedHexagon.resolution, 0); } }, [selectedHexagon, fetchHexagonProperties]); const handlePropertiesTabClick = useCallback(() => { setRightPaneTab('properties'); if (selectedHexagon?.type === 'hexagon' && properties.length === 0 && !loadingProperties) { setPropertiesOffset(0); fetchHexagonProperties(selectedHexagon.id, selectedHexagon.resolution, 0); } }, [selectedHexagon, properties.length, loadingProperties, fetchHexagonProperties]); const handleLoadMoreProperties = useCallback(() => { if (selectedHexagon && selectedHexagon.type === 'hexagon') { fetchHexagonProperties(selectedHexagon.id, selectedHexagon.resolution, propertiesOffset); } }, [selectedHexagon, propertiesOffset, fetchHexagonProperties]); const handleCloseSelection = useCallback(() => { setSelectedHexagon(null); setProperties([]); setAreaStats(null); }, []); return { selectedHexagon, properties, propertiesTotal, loadingProperties, areaStats, loadingAreaStats, hoveredHexagon, rightPaneTab, setRightPaneTab, handleHexagonClick, handleHexagonHover, handleViewPropertiesFromArea, handlePropertiesTabClick, handleLoadMoreProperties, handleCloseSelection, }; }