//
//  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)
            }
        }
    }
}
