import { defineStore } from 'pinia' import { type CreateCommentDto, type CommentDto, ResponseError, type CursorPagedCommentDto } from '~~/.api-client' import { useCommentApi } from '~/composables/comment/useCommentApi' import { useLogger } from '../app/composables/useLogger' type ApplicationFormId = string type FormElementId = string export const useCommentStore = defineStore('Comment', () => { const nextCursorByApplicationFormId = ref< Record< ApplicationFormId, Record > >({}) const commentsByApplicationFormId = ref>>({}) const countsByApplicationFormId = ref>>({}) const commentApi = useCommentApi() const loadedFormElementComments = ref(new Set()) const logger = useLogger().withTag('commentStore') async function loadInitial(applicationFormId: ApplicationFormId, formElementId: FormElementId) { initializeCursorState(applicationFormId, formElementId) const cacheKey = `${applicationFormId}:${formElementId}` if (loadedFormElementComments.value.has(cacheKey)) return nextCursorByApplicationFormId.value[applicationFormId]![formElementId]!.isLoading = true try { const page = await commentApi.getCommentsByApplicationFormId(applicationFormId, formElementId, undefined, 10) upsertComments(applicationFormId, formElementId, page, { prepend: false }) } catch (e) { nextCursorByApplicationFormId.value[applicationFormId]![formElementId]!.isLoading = false logger.error('Failed loading initial comments:', e) return } loadedFormElementComments.value.add(cacheKey) } async function loadMore(applicationFormId: ApplicationFormId, formElementId: FormElementId) { initializeCursorState(applicationFormId, formElementId) const state = nextCursorByApplicationFormId.value[applicationFormId]![formElementId]! if (state.isLoading || !state.hasMore) return state.isLoading = true const cursor = state.nextCursorCreatedAt ?? undefined try { const page = await commentApi.getCommentsByApplicationFormId(applicationFormId, formElementId, cursor, 10) // Prepend older comments when loading more (scroll up) upsertComments(applicationFormId, formElementId, page, { prepend: true }) } catch (e) { state.isLoading = false logger.error('Failed loading more comments:', e) return Promise.reject(e) } } async function createComment( applicationFormId: ApplicationFormId, formElementId: string, createCommentDto: CreateCommentDto ): Promise { try { const newComment = await commentApi.createComment(applicationFormId, formElementId, createCommentDto) const commentsByFormElement = commentsByApplicationFormId.value[applicationFormId] ?? {} if (!commentsByFormElement[formElementId]) { commentsByFormElement[formElementId] = [] } commentsByFormElement[formElementId].push(newComment) commentsByApplicationFormId.value[applicationFormId] = commentsByFormElement const currentCounts = countsByApplicationFormId.value[applicationFormId] ?? {} currentCounts[formElementId] = (currentCounts[formElementId] ?? 0) + 1 countsByApplicationFormId.value[applicationFormId] = currentCounts return newComment } catch (e: unknown) { if (e instanceof ResponseError) { logger.error('Failed creating comment:', e.response) } else { logger.error('Failed creating comment:', e) } return Promise.reject(e) } } async function updateComment(id?: string, commentDto?: CommentDto): Promise { if (!id || !commentDto) { return Promise.reject(new Error('ID or comment DTO missing')) } try { const updatedComment = await commentApi.updateComment(id, commentDto) const commentsByFormElement = commentsByApplicationFormId.value[updatedComment.applicationFormId] const formElementComments = commentsByFormElement?.[updatedComment.formElementId] // Update the comment in the store if (formElementComments) { const index = formElementComments.findIndex((comment) => comment.id === id) if (index !== -1 && formElementComments[index]) { formElementComments[index] = updatedComment } } return updatedComment } catch (e: unknown) { if (e instanceof ResponseError) { logger.error(`Failed updating comment with ID ${id}:`, e.response) } else { logger.error(`Failed updating comment with ID ${id}:`, e) } return Promise.reject(e) } } async function deleteCommentById(id: string): Promise { try { await commentApi.deleteCommentById(id) // Remove the comment from the store for (const applicationFormId in commentsByApplicationFormId.value) { const commentsByFormElement = commentsByApplicationFormId.value[applicationFormId] if (!commentsByFormElement) continue for (const formElementId in commentsByFormElement) { const formElementComments = commentsByFormElement[formElementId] if (formElementComments) { const index = formElementComments.findIndex((comment) => comment.id === id) if (index !== -1) { // Remove the comment from the array formElementComments.splice(index, 1) const commentCountsByFormElement = countsByApplicationFormId.value[applicationFormId] // Decrement the comment count for the form element if (commentCountsByFormElement && commentCountsByFormElement[formElementId] != null) { commentCountsByFormElement[formElementId] = Math.max( 0, (commentCountsByFormElement[formElementId] ?? 0) - 1 ) } return } } } } } catch (e: unknown) { if (e instanceof ResponseError) { logger.error(`Failed deleting comment with ID ${id}:`, e.response) } else { logger.error(`Failed deleting comment with ID ${id}:`, e) } return Promise.reject(e) } } function upsertComments( applicationFormId: ApplicationFormId, formElementId: FormElementId, page: CursorPagedCommentDto, options: { prepend: boolean } ) { const applicationFormComments = commentsByApplicationFormId.value[applicationFormId] ?? {} const formElementComments = applicationFormComments[formElementId] ?? (applicationFormComments[formElementId] = []) const newComments = page.content.filter((newComment) => !formElementComments.some((c) => c.id === newComment.id)) if (options.prepend) { // Prepend older comments at the beginning (they come in DESC order, so reverse to maintain chronological order) formElementComments.unshift(...newComments.reverse()) } else { // Initial load: comments come in DESC order (newest first), reverse for display (oldest at top, newest at bottom) formElementComments.push(...newComments.reverse()) } commentsByApplicationFormId.value[applicationFormId] = applicationFormComments nextCursorByApplicationFormId.value[applicationFormId]![formElementId] = { nextCursorCreatedAt: page.nextCursorCreatedAt ?? null, hasMore: page.hasMore, isLoading: false } } function initializeCursorState(applicationFormId: ApplicationFormId, formElementId: FormElementId) { if (!nextCursorByApplicationFormId.value[applicationFormId]) { nextCursorByApplicationFormId.value[applicationFormId] = {} } if (!nextCursorByApplicationFormId.value[applicationFormId]![formElementId]) { nextCursorByApplicationFormId.value[applicationFormId]![formElementId] = { nextCursorCreatedAt: null, hasMore: true, isLoading: false } } } async function loadCounts(applicationFormId: ApplicationFormId) { try { const result = await commentApi.getGroupedCommentCountByApplicationFromId(applicationFormId) countsByApplicationFormId.value[applicationFormId] = Object.fromEntries( Object.entries(result.counts ?? {}).map(([formElementId, count]) => [formElementId, Number(count)]) ) } catch (e) { logger.error('Failed loading comment counts:', e) } } return { loadCounts, loadInitial, loadMore, createComment, updateComment, deleteCommentById, commentsByApplicationFormId, nextCursorByApplicationFormId, countsByApplicationFormId } })