"use client"; import { useCallback, useEffect, useRef, useState } from "react"; import { getLocalizedCityName } from "@/lib/dashboard-home-copy"; import type { ScanOpportunityRow } from "@/lib/dashboard-types"; import type { useDashboardStore } from "@/hooks/useDashboardStore"; import { findDetailForCity, isFullEnoughForDeepAnalysis, } from "@/components/dashboard/scan-terminal/city-detail-utils"; import { findRowForCity, normalizeCityKey, prettifyCityName, } from "@/components/dashboard/scan-terminal/decision-utils"; import type { AiPinnedCity } from "@/components/dashboard/scan-terminal/types"; type DashboardStore = ReturnType; export function useAiPinnedCityWorkspace({ locale, store, timeSortedRows, }: { locale: string; store: DashboardStore; timeSortedRows: ScanOpportunityRow[]; }) { const [aiPinnedCities, setAiPinnedCities] = useState([]); const aiFullHydrationRef = useRef>(new Set()); const aiHydrationQueueRef = useRef([]); const aiHydrationRunningRef = useRef(false); const aiHydrationRetriesRef = useRef>(new Map()); const runAiHydrationQueue = useCallback(async () => { if (aiHydrationRunningRef.current) return; aiHydrationRunningRef.current = true; try { while (aiHydrationQueueRef.current.length > 0) { const nextCity = aiHydrationQueueRef.current.shift(); const key = normalizeCityKey(nextCity || ""); if (!nextCity || !key) continue; try { const detail = await store.ensureCityDetail( nextCity, false, "full", ); if (!isFullEnoughForDeepAnalysis(detail)) { const retries = aiHydrationRetriesRef.current.get(key) || 0; if (retries >= 3) { aiFullHydrationRef.current.delete(key); aiHydrationRetriesRef.current.delete(key); } else { aiHydrationRetriesRef.current.set(key, retries + 1); } } } catch { const retries = aiHydrationRetriesRef.current.get(key) || 0; if (retries >= 3) { aiFullHydrationRef.current.delete(key); aiHydrationRetriesRef.current.delete(key); } else { aiHydrationRetriesRef.current.set(key, retries + 1); } } } } finally { aiHydrationRunningRef.current = false; if (aiHydrationQueueRef.current.length > 0) { void runAiHydrationQueue(); } } }, [store.ensureCityDetail]); const queueAiFullHydration = useCallback( (cityName: string) => { const key = normalizeCityKey(cityName); if (!key || aiFullHydrationRef.current.has(key)) return; aiFullHydrationRef.current.add(key); aiHydrationQueueRef.current.push(cityName); void runAiHydrationQueue(); }, [runAiHydrationQueue], ); const addAiPinnedCity = useCallback((cityName: string) => { const cleanName = String(cityName || "").trim(); const key = normalizeCityKey(cleanName); if (!key) return; const matchedRow = findRowForCity(timeSortedRows, cleanName); const prettyName = prettifyCityName(cleanName); const displayName = matchedRow?.city_display_name || matchedRow?.display_name || getLocalizedCityName(cleanName, prettyName || cleanName, locale) || prettyName || cleanName; // Clear the hydration guard so that re-selecting this city always // triggers a fresh hydration attempt (fixes second-city loading failure). aiFullHydrationRef.current.delete(key); aiHydrationRetriesRef.current.delete(key); setAiPinnedCities((current) => { const existing = current.findIndex( (item) => normalizeCityKey(item.cityName) === key, ); const nextItem = { cityName: matchedRow?.city || cleanName, displayName, addedAt: Date.now(), }; if (existing >= 0) { const next = [...current]; next[existing] = { ...next[existing], ...nextItem }; return [ next[existing], ...next.filter((_, index) => index !== existing), ]; } return [nextItem, ...current].slice(0, 8); }); queueAiFullHydration(matchedRow?.city || cleanName); }, [locale, queueAiFullHydration, timeSortedRows]); const removeAiPinnedCity = useCallback((cityName: string) => { const key = normalizeCityKey(cityName); aiFullHydrationRef.current.delete(key); aiHydrationQueueRef.current = aiHydrationQueueRef.current.filter( (queuedCity) => normalizeCityKey(queuedCity) !== key, ); setAiPinnedCities((current) => current.filter((item) => normalizeCityKey(item.cityName) !== key), ); }, []); const refreshAiPinnedCityDetail = useCallback( async (cityName: string) => { const key = normalizeCityKey(cityName); if (key) { aiFullHydrationRef.current.delete(key); } await store.ensureCityDetail(cityName, true, "full"); }, [store.ensureCityDetail], ); useEffect(() => { aiPinnedCities.forEach((item) => { const key = normalizeCityKey(item.cityName); if (!key || aiFullHydrationRef.current.has(key)) return; const retries = aiHydrationRetriesRef.current.get(key) || 0; if (retries >= 3) return; const detail = findDetailForCity(store.cityDetailsByName, item.cityName); const needsFullHydration = !isFullEnoughForDeepAnalysis(detail); if (!needsFullHydration) return; queueAiFullHydration(item.cityName); }); }, [aiPinnedCities, queueAiFullHydration, store.cityDetailsByName]); return { addAiPinnedCity, aiPinnedCities, refreshAiPinnedCityDetail, removeAiPinnedCity, }; }