empty_adjacency <- function(nodes) {
    nodes <- unique(nodes)
    mat <- matrix(0L, nrow = length(nodes), ncol = length(nodes))
    rownames(mat) <- nodes
    colnames(mat) <- nodes
    mat
}

edge_list_to_matrix <- function(edges, nodes) {
    mat <- empty_adjacency(nodes)
    
    if (is.null(edges) || nrow(edges) == 0 || length(nodes) == 0) {
        return(mat)
    }
    
    valid_edges <- edges %>%
        filter(from %in% nodes, to %in% nodes)
    
    for (i in seq_len(nrow(valid_edges))) {
        mat[valid_edges$from[i], valid_edges$to[i]] <- 1L
    }
    
    mat
}

make_vis_nodes <- function(nodes) {
    tibble(
        id = nodes,
        label = nodes
    )
}

make_vis_edges <- function(edges) {
    if (is.null(edges) || nrow(edges) == 0) {
        return(data.frame(from = character(0), to = character(0)))
    }
    
    data.frame(
        from = edges$from,
        to = edges$to,
        stringsAsFactors = FALSE
    )
}

collect_annotations_from_store <- function(tasks, store, fallback_nodes) {
    out <- vector("list", nrow(tasks))
    names(out) <- tasks$task_id
    
    for (i in seq_len(nrow(tasks))) {
        task_id <- tasks$task_id[i]
        
        nodes_i <- store$nodes[[task_id]]
        edges_i <- store$edges[[task_id]]
        
        if (is.null(nodes_i) || length(nodes_i) == 0) {
            nodes_i <- fallback_nodes
        }
        
        if (is.null(edges_i)) {
            edges_i <- tibble(
                from = character(0),
                to = character(0)
            )
        }
        
        out[[task_id]] <- list(
            nodes = nodes_i,
            edges = edges_i,
            human_mat = edge_list_to_matrix(edges_i, nodes_i)
        )
    }
    
    out
}