import { useEffect, useMemo, useRef, useState } from 'react' import { nostrService } from '@/lib/nostr' import type { Article } from '@/types/nostr' import { applyFiltersAndSort } from '@/lib/articleFiltering' import type { ArticleFilters } from '@/components/ArticleFilters' import { t } from '@/lib/i18n' import { objectCache } from '@/lib/objectCache' import { getAuthorSponsoring } from '@/lib/sponsoring' export function useArticles(searchQuery: string = '', filters: ArticleFilters | null = null): { articles: Article[] allArticles: Article[] loading: boolean error: string | null loadArticleContent: (articleId: string, authorPubkey: string) => Promise
} { const [articles, setArticles] = useState([]) const [loading, setLoading] = useState(true) const [error, setError] = useState(null) const hasArticlesRef = useRef(false) useLoadAuthorsFromCache({ setArticles, setLoading, setError, hasArticlesRef }) const loadArticleContent = createLoadArticleContent({ setArticles, setError }) // Apply filters and sorting const filteredArticles = useMemo(() => { const effectiveFilters = filters ?? buildDefaultFilters() if (!filters && !searchQuery.trim()) { return articles } return applyFiltersAndSort(articles, searchQuery, effectiveFilters) }, [articles, searchQuery, filters]) return { articles: filteredArticles, allArticles: articles, loading, error, loadArticleContent } } function buildDefaultFilters(): { authorPubkey: null; sortBy: 'newest'; category: 'all' } { return { authorPubkey: null, sortBy: 'newest', category: 'all' } } function useLoadAuthorsFromCache(params: { setArticles: (value: Article[] | ((prev: Article[]) => Article[])) => void setLoading: (value: boolean) => void setError: (value: string | null) => void hasArticlesRef: { current: boolean } }): void { const { setArticles, setLoading, setError, hasArticlesRef } = params useEffect(() => { void loadInitialAuthors({ setArticles, setLoading, setError, hasArticlesRef }) }, [setArticles, setLoading, setError, hasArticlesRef]) } async function loadInitialAuthors(params: { setArticles: (value: Article[] | ((prev: Article[]) => Article[])) => void setLoading: (value: boolean) => void setError: (value: string | null) => void hasArticlesRef: { current: boolean } }): Promise { params.setLoading(true) params.setError(null) const hasCachedAuthors = await loadAuthorsFromCache(params) if (!hasCachedAuthors) { params.setError(t('common.error.noContent')) } } async function loadAuthorsFromCache(params: { setArticles: (value: Article[] | ((prev: Article[]) => Article[])) => void setLoading: (value: boolean) => void hasArticlesRef: { current: boolean } }): Promise { try { const authors = (await objectCache.getAll('author')) as Article[] if (authors.length === 0) { params.setLoading(false) const {hasArticlesRef} = params hasArticlesRef.current = false return false } params.setArticles((prev) => { const merged = mergeAuthorsIntoArticles({ prev, authors }) const {hasArticlesRef} = params hasArticlesRef.current = merged.length > 0 return merged }) params.setLoading(false) void updateAuthorsSponsoringFromCache({ authors, setArticles: params.setArticles }) return true } catch (loadError) { console.error('Error loading authors from cache:', loadError) params.setLoading(false) return false } } function mergeAuthorsIntoArticles(params: { prev: Article[] authors: Article[] }): Article[] { const existingIds = new Set(params.prev.map((a) => a.id)) const newAuthors = params.authors.filter((a) => !existingIds.has(a.id)) return [...params.prev, ...newAuthors].sort((a, b) => b.createdAt - a.createdAt) } async function updateAuthorsSponsoringFromCache(params: { authors: Article[] setArticles: (value: Article[] | ((prev: Article[]) => Article[])) => void }): Promise { const authorsNeedingSponsoring = params.authors.filter((a) => a.isPresentation && a.pubkey && a.totalSponsoring === undefined) if (authorsNeedingSponsoring.length === 0) { return } const sponsoringByAuthorId = await loadSponsoringByAuthorId(authorsNeedingSponsoring) params.setArticles((prev) => applySponsoringToArticles({ prev, sponsoringByAuthorId })) } async function loadSponsoringByAuthorId(authors: Article[]): Promise> { const sponsoringResults = await Promise.all(authors.map((author) => loadAuthorSponsoring(author))) return new Map(sponsoringResults.filter((r): r is { authorId: string; totalSponsoring: number } => Boolean(r)).map((r) => [r.authorId, r.totalSponsoring])) } async function loadAuthorSponsoring(author: Article): Promise<{ authorId: string; totalSponsoring: number } | null> { if (!author.pubkey) { return null } const totalSponsoring = await getAuthorSponsoring(author.pubkey, true) return { authorId: author.id, totalSponsoring } } function applySponsoringToArticles(params: { prev: Article[]; sponsoringByAuthorId: Map }): Article[] { return params.prev.map((article) => { const totalSponsoring = params.sponsoringByAuthorId.get(article.id) return totalSponsoring !== undefined && article.isPresentation ? { ...article, totalSponsoring } : article }) } function createLoadArticleContent(params: { setArticles: (value: Article[] | ((prev: Article[]) => Article[])) => void setError: (value: string | null) => void }): (articleId: string, authorPubkey: string) => Promise
{ return async (articleId: string, authorPubkey: string): Promise
=> { try { const article = await nostrService.getArticleById(articleId) if (!article) { return null } const decryptedContent = await nostrService.getDecryptedArticleContent(articleId, authorPubkey) if (decryptedContent) { params.setArticles((prev) => prev.map((a) => (a.id === articleId ? { ...a, content: decryptedContent, paid: true } : a))) } return article } catch (e) { console.error('Error loading article content:', e) params.setError(e instanceof Error ? e.message : 'Failed to load article') return null } } }