############################################################
# graph and display helpers
############################################################


def neighbors(graph, node):
    return graph.get(node, [])


def path_to_string(path):
    """path is a list of nodes"""
    if path is None:
        return "None"
    node_strs = []
    for node in path:
        node_strs.append(str(node))
    return " --> ".join(node_strs)


def pathlist_to_string(queue):
    """path is a list of nodes"""
    path_strs = []
    for path in queue:
        path_strs.append(path_to_string(path))
    return "[" + ", ".join(path_strs) + "]"


############################################################
# finding paths in trees using depth-first search
############################################################


def dfs_tree_helper(graph, goal, path):
    print("Current DFS path:", path_to_string(path))

    # stop if reached goal
    current_node = path[-1]
    if current_node == goal:
        return path

    # check if goal in subtrees
    for next_node in neighbors(graph, current_node):
        result = dfs_tree_helper(graph, goal, path + [next_node])
        if result is not None:
            return result

    # no nodes in subtree are goal, backtrack to parent/caller
    return None


def dfs_tree(graph, start, goal):
    return dfs_tree_helper(graph, goal, [start])


def demo_dfs_tree():
    filesystem = {
        "home": ["school", "personal", "downloads"],
        "school": ["fall24", "spring25", "fall25"],
        "fall25": ["6.100", "8.01"],
        "personal": ["photos", "bills"],
        "downloads": ["slides.pdf", "ps1", "beach.jpg"],
        "ps1": ["pset.py", "test.py"],
    }

    print(dfs_tree(filesystem, "home", "pset.py"))
    print()
    print(dfs_tree(filesystem, "home", "beach.jpg"))
    print()

    # make "downloads" first child of "home"
    folders = filesystem["home"]
    filesystem["home"] = [folders[-1]] + folders[:-1]

    print(dfs_tree(filesystem, "home", "pset.py"))
    print()
    print(dfs_tree(filesystem, "home", "beach.jpg"))
    print()


# demo_dfs_tree()


############################################################
# finding paths in general graphs using depth-first search
############################################################


def dfs_graph_helper(graph, goal, path):
    print("Current DFS path:", path_to_string(path))

    current_node = path[-1]
    if current_node == goal:
        return path

    for next_node in neighbors(graph, current_node):

        # avoid self-loops
        if next_node in path:
            print(f"  AVOID self-loop from {current_node} to {next_node}")
            continue

        result = dfs_graph_helper(graph, goal, path + [next_node])
        if result is not None:
            return result

    return None


def dfs_graph(graph, start, goal):
    return dfs_graph_helper(graph, goal, [start])


def demo_dfs_graph():
    flights = {
        "Boston": ["Providence", "New York"],
        "Providence": ["Boston", "New York"],
        "New York": ["Chicago"],
        "Chicago": ["Denver", "Phoenix"],
        "Denver": ["New York", "Phoenix"],
        "Los Angeles": ["Boston"],
    }

    print(dfs_graph(flights, "Boston", "Phoenix"))
    print()

    # change the ordering in which we explore Chicago's neighbors
    flights["Chicago"].reverse()

    print(dfs_graph(flights, "Boston", "Phoenix"))
    print()


# demo_dfs_graph()
