# CollabCommentApplicationService.java

    package com.yumu.noveltranslator.application.service;

    import com.yumu.noveltranslator.port.dto.collab.CommentResponse;
    import com.yumu.noveltranslator.port.dto.collab.CreateCommentRequest;
    import com.yumu.noveltranslator.domain.model.CollabComment;
    import com.yumu.noveltranslator.domain.model.User;
    import com.yumu.noveltranslator.enums.ErrorCodeEnum;
    import com.yumu.noveltranslator.exception.BusinessException;
    import com.yumu.noveltranslator.port.out.CollaborationRepositoryPort;
    import com.yumu.noveltranslator.port.out.UserRepositoryPort;
    import com.yumu.noveltranslator.port.dto.common.PageResult;
    import lombok.RequiredArgsConstructor;
    import lombok.extern.slf4j.Slf4j;
    import org.springframework.stereotype.Service;

    import java.util.ArrayList;
    import java.util.HashMap;
    import java.util.HashSet;
    import java.util.List;
    import java.util.Map;
    import java.util.Set;
    import java.util.stream.Collectors;

    /**
     * 协作评论应用服务
     */
    @Service
    @Slf4j
    @RequiredArgsConstructor
    public class CollabCommentApplicationService implements com.yumu.noveltranslator.port.in.CollabCommentPort {

        private final CollaborationRepositoryPort collabPort;
        private final UserRepositoryPort userPort;

        /**
         * 创建评论
         */
        public CommentResponse createComment(Long chapterTaskId, Long userId, CreateCommentRequest request) {
            var task = collabPort.findChapterTaskById(chapterTaskId);
            if (task.isEmpty()) {
                throw new BusinessException(ErrorCodeEnum.NOT_FOUND, "章节不存在: " + chapterTaskId);
            }
            var member = collabPort.findMemberByProjectAndUser(task.get().getProjectId(), userId);
            if (member == null) {
                throw new BusinessException(ErrorCodeEnum.FORBIDDEN, "无权访问该项目");
            }

            // 如果是回复，验证父评论是否存在
            if (request.getParentId() != null) {
                var parent = collabPort.findCommentById(request.getParentId());
                if (parent.isEmpty() || !parent.get().getChapterTaskId().equals(chapterTaskId)) {
                    throw new BusinessException(ErrorCodeEnum.NOT_FOUND, "父评论不存在");
                }
            }

            CollabComment comment = new CollabComment();
            comment.setChapterTaskId(chapterTaskId);
            comment.setUserId(userId);
            comment.setSourceText(request.getSourceText());
            comment.setTargetText(request.getTargetText());
            comment.setContent(request.getContent());
            comment.setParentId(request.getParentId());
            comment.setResolved(false);
            collabPort.saveComment(comment);

            log.info("创建评论: chapterTaskId={}, userId={}, parentId={}", chapterTaskId, userId, request.getParentId());
            return toCommentResponse(comment, Map.of());
        }

        /**
         * 获取章节评论列表（分页，树形结构）
         */
        public PageResult<CommentResponse> getCommentsByChapterPage(Long chapterTaskId, Long userId, int page, int size) {
            var task = collabPort.findChapterTaskById(chapterTaskId);
            if (task.isEmpty()) {
                throw new BusinessException(ErrorCodeEnum.NOT_FOUND, "章节不存在: " + chapterTaskId);
            }
            var member = collabPort.findMemberByProjectAndUser(task.get().getProjectId(), userId);
            if (member == null) {
                throw new BusinessException(ErrorCodeEnum.FORBIDDEN, "无权访问该项目");
            }

            // 分页查询根评论
            var rootPage = collabPort.findCommentsByChapterTaskIdPaged(chapterTaskId, page, size);

            // 批量加载回复和用户，避免 N+1
            Set<Long> userIds = new HashSet<>();
            Map<Long, List<CollabComment>> repliesByRoot = new HashMap<>();
            for (CollabComment root : rootPage.getRecords()) {
                userIds.add(root.getUserId());
                List<CollabComment> replies = collabPort.findRepliesByParentId(root.getId());
                repliesByRoot.put(root.getId(), replies);
                for (CollabComment reply : replies) {
                    userIds.add(reply.getUserId());
                }
            }

            Map<Long, User> userMap = new HashMap<>();
            if (!userIds.isEmpty()) {
                for (Long uid : userIds) {
                    userPort.findById(uid).ifPresent(u -> userMap.put(uid, u));
                }
            }

            List<CommentResponse> responses = rootPage.getRecords().stream()
                    .map(root -> {
                        CommentResponse resp = toCommentResponse(root, userMap);
                        List<CommentResponse> replyResponses = repliesByRoot.getOrDefault(root.getId(), List.of()).stream()
                                .map(reply -> toCommentResponse(reply, userMap))
                                .collect(Collectors.toList());
                        resp.setReplies(replyResponses);
                        return resp;
                    })
                    .collect(Collectors.toList());

            PageResult<CommentResponse> resultPage = new PageResult<>(responses, rootPage.getTotal(), (long) page, (long) size);
            return resultPage;
        }

        /**
         * 标记评论已解决
         */
        public void resolveComment(Long commentId, Long userId) {
            CollabComment comment = collabPort.findCommentById(commentId).orElse(null);
            if (comment == null) {
                throw new BusinessException(ErrorCodeEnum.NOT_FOUND, "评论不存在");
            }
            var task = collabPort.findChapterTaskById(comment.getChapterTaskId());
            if (task.isEmpty()) {
                throw new BusinessException(ErrorCodeEnum.NOT_FOUND, "章节不存在");
            }
            var member = collabPort.findMemberByProjectAndUser(task.get().getProjectId(), userId);
            if (member == null) {
                throw new BusinessException(ErrorCodeEnum.FORBIDDEN, "无权访问该项目");
            }
            comment.setResolved(true);
            collabPort.updateComment(comment);
        }

        /**
         * 删除评论
         */
        public void deleteComment(Long commentId, Long userId) {
            CollabComment comment = collabPort.findCommentById(commentId).orElse(null);
            if (comment == null) {
                throw new BusinessException(ErrorCodeEnum.NOT_FOUND, "评论不存在");
            }
            var task = collabPort.findChapterTaskById(comment.getChapterTaskId());
            if (task.isEmpty()) {
                throw new BusinessException(ErrorCodeEnum.NOT_FOUND, "章节不存在");
            }
            var member = collabPort.findMemberByProjectAndUser(task.get().getProjectId(), userId);
            if (member == null) {
                throw new BusinessException(ErrorCodeEnum.FORBIDDEN, "无权访问该项目");
            }
            // 仅创建者可以删除
            if (!comment.getUserId().equals(userId)) {
                throw new BusinessException(ErrorCodeEnum.FORBIDDEN, "无权删除他人评论");
            }
            collabPort.deleteComment(commentId);
        }

        private CommentResponse toCommentResponse(CollabComment comment, Map<Long, User> userMap) {
            CommentResponse resp = new CommentResponse();
            resp.setId(comment.getId());
            resp.setUserId(comment.getUserId());
            resp.setSourceText(comment.getSourceText());
            resp.setTargetText(comment.getTargetText());
            resp.setContent(comment.getContent());
            resp.setResolved(comment.getResolved());
            resp.setCreateTime(comment.getCreateTime());

            User user = userMap.get(comment.getUserId());
            if (user != null) {
                resp.setUsername(user.getUsername());
                resp.setAvatar(user.getAvatar());
            }
            resp.setReplies(new ArrayList<>());
            return resp;
        }
    }