Tusker/Tusker/Screens/Conversation/ConversationTree.swift

102 lines
3.5 KiB
Swift

//
// ConversationTree.swift
// Tusker
//
// Created by Shadowfacts on 2/4/23.
// Copyright © 2023 Shadowfacts. All rights reserved.
//
import Foundation
import Pachyderm
class ConversationNode {
let status: StatusMO
var children: [ConversationNode]
init(status: StatusMO) {
self.status = status
self.children = []
}
}
struct ConversationTree {
let ancestors: [ConversationNode]
let mainStatus: ConversationNode
var descendants: [ConversationNode] {
mainStatus.children
}
init(ancestors: [ConversationNode], mainStatus: ConversationNode) {
self.ancestors = ancestors
self.mainStatus = mainStatus
}
static func build(for mainStatus: StatusMO, ancestors: [StatusMO], descendants: [StatusMO]) -> ConversationTree {
let mainStatusNode = ConversationNode(status: mainStatus)
let ancestors = buildAncestorNodes(mainStatusNode: mainStatusNode, ancestors: ancestors)
buildDescendantNodes(mainStatusNode: mainStatusNode, descendants: descendants)
return ConversationTree(ancestors: ancestors, mainStatus: mainStatusNode)
}
private static func buildAncestorNodes(mainStatusNode: ConversationNode, ancestors: [StatusMO]) -> [ConversationNode] {
var statuses = ancestors
var parents = [ConversationNode]()
var parentID: String? = mainStatusNode.status.inReplyToID
while let currentParentID = parentID,
let parentIndex = statuses.firstIndex(where: { $0.id == currentParentID }) {
let parentStatus = statuses.remove(at: parentIndex)
let node = ConversationNode(status: parentStatus)
parents.insert(node, at: 0)
parentID = parentStatus.inReplyToID
}
// once the parents list is built and in-order, then we walk through and set each node's children
for (index, node) in parents.enumerated() {
if index == parents.count - 1 {
// the last parent is the direct parent of the main status
node.children = [mainStatusNode]
} else {
// otherwise, it's the parent of the status that comes immediately after it in the parents list
node.children = [parents[index + 1]]
}
}
return parents
}
// doesn't return anything, since we're modifying the main status node in-place
private static func buildDescendantNodes(mainStatusNode: ConversationNode, descendants: [StatusMO]) {
var descendants = descendants
func removeAllInReplyTo(id: String) -> [StatusMO] {
let statuses = descendants.filter { $0.inReplyToID == id }
descendants.removeAll { $0.inReplyToID == id }
return statuses
}
var nodes: [String: ConversationNode] = [
mainStatusNode.status.id: mainStatusNode
]
var idsToCheck = [mainStatusNode.status.id]
while !idsToCheck.isEmpty {
let inReplyToID = idsToCheck.removeFirst()
let nodeForID = nodes[inReplyToID]!
let inReply = removeAllInReplyTo(id: inReplyToID)
for reply in inReply {
idsToCheck.append(reply.id)
let replyNode = ConversationNode(status: reply)
nodes[reply.id] = replyNode
nodeForID.children.append(replyNode)
}
}
}
}