review.ts

  1import { writable, derived } from 'svelte/store';
  2import type { ReviewThread, Review } from '$lib/types/index';
  3import { fileCommentCounts } from './files.ts';
  4
  5export const threads = writable<ReviewThread[]>([]);
  6export const reviewStatus = writable<Review['status']>('pending');
  7
  8export const threadsByFile = derived(threads, ($threads) => {
  9	const map = new Map<string, ReviewThread[]>();
 10	for (const t of $threads) {
 11		const existing = map.get(t.filePath) || [];
 12		existing.push(t);
 13		map.set(t.filePath, existing);
 14	}
 15	return map;
 16});
 17
 18export const stats = derived(threads, ($threads) => {
 19	const files = new Set($threads.map((t) => t.filePath));
 20	return {
 21		totalThreads: $threads.length,
 22		totalComments: $threads.reduce((sum, t) => sum + t.comments.length, 0),
 23		filesWithComments: files.size,
 24		unresolvedThreads: $threads.filter((t) => !t.resolved).length
 25	};
 26});
 27
 28export async function loadComments(filePath?: string) {
 29	const params = filePath ? `?file=${encodeURIComponent(filePath)}` : '';
 30	const res = await fetch(`/api/comments${params}`);
 31	const data = await res.json();
 32	if (data.threads) {
 33		if (filePath) {
 34			// Merge with existing threads for other files
 35			threads.update((existing) => {
 36				const otherFiles = existing.filter((t) => t.filePath !== filePath);
 37				return [...otherFiles, ...data.threads];
 38			});
 39		} else {
 40			threads.set(data.threads);
 41		}
 42	}
 43	// Update comment counts
 44	updateCommentCounts();
 45}
 46
 47function updateCommentCounts() {
 48	threads.subscribe(($threads) => {
 49		const counts: Record<string, number> = {};
 50		for (const t of $threads) {
 51			counts[t.filePath] = (counts[t.filePath] || 0) + 1;
 52		}
 53		fileCommentCounts.set(counts);
 54	})();
 55}
 56
 57export async function addComment(
 58	filePath: string,
 59	lineNumber: number,
 60	side: 'old' | 'new',
 61	text: string,
 62	type: 'comment' | 'suggestion' | 'question' = 'comment',
 63	threadId?: string,
 64	originalLineContent?: string
 65) {
 66	const res = await fetch('/api/comments', {
 67		method: 'POST',
 68		headers: { 'Content-Type': 'application/json' },
 69		body: JSON.stringify({ filePath, lineNumber, side, text, type, threadId, originalLineContent })
 70	});
 71	const thread = await res.json();
 72	if (thread.id) {
 73		threads.update((existing) => {
 74			const idx = existing.findIndex((t) => t.id === thread.id);
 75			if (idx >= 0) {
 76				existing[idx] = thread;
 77				return [...existing];
 78			}
 79			return [...existing, thread];
 80		});
 81		updateCommentCounts();
 82	}
 83	return thread;
 84}
 85
 86export async function resolveThread(threadId: string) {
 87	await fetch('/api/comments', {
 88		method: 'PATCH',
 89		headers: { 'Content-Type': 'application/json' },
 90		body: JSON.stringify({ threadId, action: 'resolve' })
 91	});
 92	threads.update((existing) => {
 93		const thread = existing.find((t) => t.id === threadId);
 94		if (thread) thread.resolved = !thread.resolved;
 95		return [...existing];
 96	});
 97}
 98
 99export async function deleteThread(threadId: string) {
100	await fetch('/api/comments', {
101		method: 'DELETE',
102		headers: { 'Content-Type': 'application/json' },
103		body: JSON.stringify({ threadId })
104	});
105	threads.update((existing) => existing.filter((t) => t.id !== threadId));
106	updateCommentCounts();
107}
108
109export async function checkOutdated() {
110	const res = await fetch('/api/comments', {
111		method: 'PATCH',
112		headers: { 'Content-Type': 'application/json' },
113		body: JSON.stringify({ action: 'check-outdated' })
114	});
115	const data = await res.json();
116	if (data.threads) {
117		threads.set(data.threads);
118		updateCommentCounts();
119	}
120	return data.outdatedCount || 0;
121}
122
123export async function submitReview(status: Review['status'], summary?: string) {
124	const res = await fetch('/api/review', {
125		method: 'POST',
126		headers: { 'Content-Type': 'application/json' },
127		body: JSON.stringify({ status, summary })
128	});
129	const review = await res.json();
130	reviewStatus.set(review.status);
131	return review;
132}