'use client'; import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; import type { Comment, CommentTag, Version, VideoData } from '@/components/video-page/types'; interface UseVideoPageDataParams { mode: 'dashboard' | 'watch'; videoId: string; propProjectId?: string; } export function useVideoPageData({ mode, videoId, propProjectId, }: UseVideoPageDataParams) { const [video, setVideo] = useState(null); const [loading, setLoading] = useState(true); const [error, setError] = useState(''); const [activeVersionId, setActiveVersionId] = useState(null); const [availableTags, setAvailableTags] = useState([]); const [selectedTagId, setSelectedTagId] = useState(null); const commentsEtagRef = useRef>(new Map()); const apiBasePath = useMemo(() => { return mode === 'dashboard' ? `/api/projects/${propProjectId}/videos/${videoId}?includeComments=false` : `/api/watch/${videoId}`; }, [mode, propProjectId, videoId]); const projectId = propProjectId || video?.projectId; const fetchVersionComments = useCallback(async (versionId: string, useEtag: boolean) => { const headers: HeadersInit = {}; if (useEtag) { const etag = commentsEtagRef.current.get(versionId); if (etag) headers['If-None-Match'] = etag; } const res = await fetch(`/api/versions/${versionId}/comments?includeResolved=true`, { cache: 'no-store', headers, }); if (res.status === 304) return; if (!res.ok) return; const etag = res.headers.get('etag'); if (etag) commentsEtagRef.current.set(versionId, etag); const payload = await res.json(); const commentsList = payload?.data?.comments; if (!Array.isArray(commentsList)) return; setVideo((prev) => { if (!prev) return prev; const totalComments = commentsList.reduce((sum: number, comment: Comment) => { return sum + 1 + (comment.replies?.length ?? 0); }, 0); return { ...prev, versions: prev.versions.map((version) => ( version.id === versionId ? { ...version, comments: commentsList, _count: { comments: totalComments } } : version )), }; }); }, []); useEffect(() => { async function fetchVideo() { try { const res = await fetch(apiBasePath, { cache: 'no-store' }); if (!res.ok) { const errorText = mode === 'dashboard' ? await res.text() : ''; setError(mode === 'dashboard' ? `Failed to load video: ${res.status} ${errorText}` : 'Video not found or access denied' ); setLoading(false); return; } const response = await res.json(); const rawData = response.data as Omit & { versions?: Array; }; const normalizedData: VideoData = { ...rawData, versions: (rawData.versions || []).map((version) => ({ ...version, comments: Array.isArray(version.comments) ? version.comments : [], })), }; setVideo(normalizedData); const active = normalizedData.versions?.find((v) => v.isActive) || normalizedData.versions?.[0]; if (active) setActiveVersionId(active.id); } catch (err) { console.error('Error fetching video:', err); setError('Failed to load video'); } finally { setLoading(false); } } void fetchVideo(); }, [apiBasePath, mode]); useEffect(() => { if (!activeVersionId) return; void fetchVersionComments(activeVersionId, true); }, [activeVersionId, fetchVersionComments]); useEffect(() => { if (!projectId) return; async function fetchTags() { try { const query = videoId ? `?videoId=${encodeURIComponent(videoId)}` : ''; const res = await fetch(`/api/projects/${projectId}/tags${query}`); if (res.ok) { const data = await res.json(); const tags = data.data || []; setAvailableTags(tags); if (tags.length > 0 && !selectedTagId) { setSelectedTagId(tags[0].id); } } } catch { // noop } } void fetchTags(); }, [projectId, selectedTagId, videoId]); return { video, setVideo, loading, error, activeVersionId, setActiveVersionId, availableTags, setAvailableTags, selectedTagId, setSelectedTagId, projectId, fetchVersionComments, }; }