import APIClient
import Foundation

/*
    This will be used in the cache dictionary in CommentClientV2
    to store comments as a result of requests.

    These will then be used in various ways to display comments
    when the channel has updated.
 */
public struct CommentsSheet: Equatable {
    public var entityId: String
    public var entityType: CommentEntity.CommentEntityType
    public var nextPageCursor: String?
    public var allowComment: Bool
    public var disableReason: String?

    // The reply cursor for each comment
    public var replyNextPageCursor: [String: String] = [:]

    // Value Source
    private var comments: Set<CommentEntity> = []

    // Processed Values - Maintain sorted arrays to avoid expensive re-sorting
    private var commentsMap: [String: CommentEntity] = [:]
    private var unparentedCommentsInSortedOrder: [String] = []
    private var replyMapInSortedOrder: [String: [String]] = [:]
    public private(set) var hasMoreRepliesMap: [String: Bool] = [:]

    public func commentById(_ commentID: String) -> CommentEntity? {
        return commentsMap[commentID]
    }

    public var allSortedUnparentedComments: [CommentEntity] {
        return unparentedCommentsInSortedOrder.compactMap { commentID in commentsMap[commentID] }
    }

    public var allSortedRepliesForComment: [String: [CommentEntity]] {
        var allRepliesMap: [String: [CommentEntity]] = [:]
        for parentCommentID in unparentedCommentsInSortedOrder {
            let replyCommentIDs: [String] = replyMapInSortedOrder[parentCommentID] ?? []
            let mappedReplies = replyCommentIDs.compactMap { commentID in commentsMap[commentID] }
            allRepliesMap[parentCommentID] = mappedReplies
        }
        return allRepliesMap
    }

    public func sortedRepliesForComment(_ parentCommentID: String) -> [CommentEntity] {
        let replyCommentIDs: [String] = replyMapInSortedOrder[parentCommentID] ?? []
        return replyCommentIDs.compactMap { commentID in commentsMap[commentID] }
    }

    mutating func addCommentsPage(_ newPage: CommentsSheetPage) {
        nextPageCursor = newPage.nextCursor
        allowComment = newPage.allowComment
        disableReason = newPage.disableReason

        // Add all parent comments
        comments.formUnion(newPage.results)

        // Store reply continuation tokens
        for newComment in newPage.results {
            if let replyContinuationToken = newComment.replyContinuationToken {
                replyNextPageCursor[newComment.id] = replyContinuationToken
            }
        }
        prepItem()
    }

    mutating func addComment(_ newComment: CommentEntity) {
        comments.insert(newComment)
        if let replyContinuationToken = newComment.replyContinuationToken {
            replyNextPageCursor[newComment.id] = replyContinuationToken
        }
        prepItem()
    }

    mutating func addRepliesPage(_ newPage: CommentSheetRepliesPage, rootCommentID: String) {
        if let cursor = newPage.replyContinuationToken {
            replyNextPageCursor[rootCommentID] = cursor
        } else {
            replyNextPageCursor.removeValue(forKey: rootCommentID)
        }
        comments.formUnion(newPage.replies)
        prepItem()
    }

    public func replyCursorForComment(_ commentID: String) -> String? {
        return replyNextPageCursor[commentID]
    }

    public func hasNextPageToLoad(_ commentID: String) -> Bool {
        let replyIDs = replyMapInSortedOrder[commentID] ?? []
        let replyCursorForComment = replyCursorForComment(commentID)
        let replyCount = replyIDs.count
        return !(replyCount == 0 || replyCursorForComment == nil)
    }

    mutating func updateCommentReaction(
        _ commentID: String,
        _ reaction: CommentReactionType,
        _ numLikes: Int
    ) {
        guard let originalComment = commentById(commentID) else { return }
        var mutableCopy = originalComment
        mutableCopy.numLikes = numLikes
        mutableCopy.reactionType = reaction.propertyValue
        comments.remove(originalComment)
        comments.insert(mutableCopy)
        commentsMap[commentID] = mutableCopy
        // No need to call prepItem() for reaction updates - just update the map
    }

    mutating func deleteComment(_ commentID: String) {
        // This assumes you will be deleting all replies as well as the parent comment
        comments = comments.filter { !($0.id == commentID || $0.parentId == commentID) }

        /*
            Cache sometimes carries a nested copy of a comment
            in .replies on a comment and potentially a duplicated
            in paged replies response

            Remove the one that is nested inside the replies of a comment
            as well so it doesn't get readded, by existing in two places.

            **Note** this looks like O(N^2) but .replies on comment
            will only ever have 1 item in it from paging response
            so it is really just O(N) on comments
         */
        if var entityWithReplyToDelete = comments.first(where: { $0.replies.first { $0.id == commentID } != nil }) {
            comments.remove(entityWithReplyToDelete)
            entityWithReplyToDelete.replies.removeAll { $0.id == commentID }
            comments.insert(entityWithReplyToDelete)
        }

        commentsMap.removeValue(forKey: commentID)
        prepItem()
    }

    init(entityId: String,
         entityType: CommentEntity.CommentEntityType,
         nextPageCursor: String? = nil,
         comments: [CommentEntity],
         allowComment: Bool,
         disableReason: String? = nil)
    {
        self.entityId = entityId
        self.entityType = entityType
        self.nextPageCursor = nextPageCursor
        self.comments = Set(comments)
        self.allowComment = allowComment
        self.disableReason = disableReason
        prepItem()
    }
}

private extension CommentsSheet {
    mutating func prepItem() {
        var newCommentsMap: [String: CommentEntity] = [:]
        var unparentedSet: Set<String> = []
        var parentedMap: [String: Set<String>] = [:]
        var newHasMoreCommentsMap: [String: Bool] = [:]

        // Process all comments
        for comment in comments {
            newCommentsMap[comment.id] = comment
            if let parentID = comment.parentId {
                var parentCommentSet: Set<String> = parentedMap[parentID] ?? []
                parentCommentSet.insert(comment.id)
                parentedMap[parentID] = parentCommentSet
            } else {
                unparentedSet.insert(comment.id)
                // Initial calculation - will be recalculated after processing nested replies
                newHasMoreCommentsMap[comment.id] = replyNextPageCursor[comment.id] != nil
            }

            for reply in comment.replies {
                newCommentsMap[reply.id] = reply
                // Add to parented map
                var parentCommentSet: Set<String> = parentedMap[comment.id] ?? []
                parentCommentSet.insert(reply.id)
                parentedMap[comment.id] = parentCommentSet
            }
        }

        // Recalculate hasMoreRepliesMap after processing nested replies
        for comment in comments where comment.parentId == nil {
            let hasReplyCursor = replyNextPageCursor[comment.id] != nil
            let currentReplyCount = (parentedMap[comment.id] ?? []).count
            let hasMoreReplies = hasReplyCursor || (comment.numReplies > currentReplyCount)
            newHasMoreCommentsMap[comment.id] = hasMoreReplies
        }

        hasMoreRepliesMap = newHasMoreCommentsMap
        commentsMap = newCommentsMap

        // Optimize sorting by only sorting when necessary
        let unparentedComments: [CommentEntity] = unparentedSet
            .compactMap { commentID in commentsMap[commentID] }

        // Use insertion sort for better performance on small arrays
        unparentedCommentsInSortedOrder = unparentedComments
            .sorted(by: { $0.createdAt > $1.createdAt })
            .map { $0.id }

        var newReplyMap: [String: [String]] = [:]

        for (parentID, replySet) in parentedMap {
            let parentedSet = replySet
                .compactMap { commentID in commentsMap[commentID] }
            let newOrderedReplies: [String] = parentedSet
                .sorted(by: { $0.createdAt < $1.createdAt })
                .map { $0.id }
            // Oldest First
            newReplyMap[parentID] = newOrderedReplies
        }

        replyMapInSortedOrder = newReplyMap
    }
}
