import json
import networkx as nx
import unicodedata
import subprocess
import os

# Function to sanitize node names for Graphviz and HTML
def sanitize_name(name):
    # Normalize to ASCII, removing accents and special characters
    return ''.join(c for c in unicodedata.normalize('NFKD', name)
                    if unicodedata.category(c) != 'Mn')

# Load JSON data from file
try:
    with open("friends8.json", "r") as f:
        data = json.load(f)
    print("Successfully loaded friends8.json")
except FileNotFoundError:
    print("Error: friends8.json not found. Please ensure the file exists in the working directory.")
    exit(1)
except json.JSONDecodeError:
    print("Error: Invalid JSON format in friends8.json.")
    exit(1)

# Create an undirected graph
G = nx.Graph()

# Add edges from the JSON data
for person, friends in data.items():
    for friend in friends:
        G.add_edge(person, friend)

# Add new authentic connections
new_connections = [
    # ("Caitlin Clark", "Kelsey Mitchell"),  # WNBA teammates
    # ("MrBeast", "Elon Musk"),  # Influencer collaboration
    # ("Charli D'Amelio", "Kim Kardashian"),  # Social media collaboration
    # ("Kim Kardashian", "Elon Musk"),  # Public figure interaction
    # ("Caitlin Clark", "Sabrina Ionescu"),  # WNBA connection
    # ("Aaron Judge", "Alex Rodriguez"),  # Yankees mentorship
    # ("Adin Ross", "MrBeast"),  # Streaming collaboration
    # ("Alex Morgan", "Serena Williams"),  # Athlete endorsement/event
    # ("Hasan Piker", "Joe Rogan"),  # Podcasting community
    # ("Jennifer Lawrence", "Robert Downey Jr."),  # Marvel franchise
    # ("Orlando Bloom", "Katy Perry"),  # Engaged couple
    # ("Harrison Ford", "Mark Hamill"),  # Star Wars co-stars
    # ("Willie Nelson", "Bruce Springsteen"),  # Farm Aid collaboration
    # ("J.K. Rowling", "Emma Watson"),  # Harry Potter franchise
    # ("Emma Watson", "Eddie Redmayne"),  # Harry Potter/Fantastic Beasts
    # ("Tyra Banks", "Kim Kardashian"),  # Reality TV/fashion
    # ("Jackie Chan", "Chris Tucker"),  # Rush Hour co-stars
    # ("Mick Jagger", "Elton John"),  # Music events
    # ("Michael B. Jordan", "Ryan Coogler"),  # Creed/Black Panther
    # ("Mark Zuckerberg", "Elon Musk"),  # Tech mogul interactions
    # ("Barack Obama", "Beyoncé"),  # Political events/endorsements
    # ("Arnold Schwarzenegger", "Sylvester Stallone"),  # Expendables co-stars
    # ("Conan O’Brien", "Will Ferrell"),  # Late-night show appearances
    # ("Jon Stewart", "John Oliver"),  # The Daily Show
    # ("Justin Trudeau", "Barack Obama"),  # Diplomatic friendship
    # ("Serena Williams", "Venus Williams"),  # Sisters/tennis partners
    # ("Stephen Hawking", "Neil deGrasse Tyson"),  # Science communication
    # ("Caitlin Clark", "Kim Kardashian"),  # SKIMS/endorsement
    # ("Aaron Judge", "Derek Jeter"),  # Yankees mentorship
    # ("Alex Morgan", "Beyoncé"),  # Women’s empowerment/endorsements
    # ("Conan O’Brien", "Adam Sandler"),  # Late-night show appearances
    # ("Chadwick Boseman", "Robert Downey Jr."),  # Avengers co-stars
    # ("Keith Richards", "Paul McCartney"),  # Music legends
    # ("Daniel Craig", "Chris Pine"),  # Hollywood events
    # ("Rachel Maddow", "Trevor Noah"),  # Political commentary
    # ("Numberphile", "Veritasium")  # Science YouTube collaboration
]
G.add_edges_from(new_connections)
print("Added new connections:", new_connections)

# Find connected components
components = list(nx.connected_components(G))
print(f"Number of connected components after adding edges: {len(components)}")

# List nodes by degree (number of connections)
degree_list = sorted(G.degree(), key=lambda x: x[1], reverse=True)
print("\nNodes sorted by number of connections (top 10 or all if fewer):")
for i, (node, degree) in enumerate(degree_list[:10], 1):
    print(f"{i}. {node}: {degree} connections")

# Print component details
print("\nComponent details:")
for i, component in enumerate(components, 1):
    print(f"Component {i} (size {len(component)}): {component}")

# Check if the graph is fully connected
if len(components) == 1:
    print("Graph is now a single connected tree with no islanded trees.")
else:
    print(f"Graph still has {len(components)} components. Some islanders remain as expected.")

# Check Katteyes' status
if "Katteyes" in G.nodes():
    for component in components:
        if "Katteyes" in component:
            print(f"Katteyes is in a component of size {len(component)}, connected to: {list(G.neighbors('Katteyes'))}")
            break
else:
    print("Katteyes is not in the graph.")

# Prompt user for root node with component representatives
print("\nAvailable components (first node as representative):")
for i, component in enumerate(components, 1):
    first_node = next(iter(component))  # Get first node in component
    print(f"{i}: {first_node} (size {len(component)})")
print(f"\nEnter a component number (1-{len(components)}) or a node name (e.g., 'Kim Kardashian'), or press Enter for largest component:")
user_input = input().strip()

# Select component to visualize
selected_component = None
if user_input:
    try:
        # Try parsing input as a component number
        component_idx = int(user_input) - 1
        if 0 <= component_idx < len(components):
            selected_component = components[component_idx]
            first_node = next(iter(selected_component))
            print(f"Selected component {component_idx + 1} containing {first_node} (size {len(selected_component)}): {selected_component}")
        else:
            print(f"Error: Component number must be between 1 and {len(components)}. Defaulting to largest component.")
            selected_component = max(components, key=len)
    except ValueError:
        # Try parsing input as a node name
        for component in components:
            if user_input in component:
                selected_component = component
                print(f"Selected component containing {user_input} (size {len(component)}): {component}")
                break
        if not selected_component:
            print(f"Error: Node '{user_input}' not found in the graph. Defaulting to largest component.")
            selected_component = max(components, key=len)
else:
    selected_component = max(components, key=len)
    first_node = next(iter(selected_component))
    print(f"Selected largest component containing {first_node} (size {len(selected_component)}): {selected_component}")

# Prompt for output file type
print("\nEnter output file type for selected component (png, svg, pdf) [default: svg]:")
file_type = input().strip().lower()
if file_type not in ['png', 'svg', 'pdf']:
    print(f"Invalid file type '{file_type}'. Defaulting to svg.")
    file_type = 'svg'

# Generate DOT file for the entire graph
dot_lines = ["graph G {"]
dot_lines.append("  rankdir=LR;")  # Horizontal layout
dot_lines.append("  node [shape=circle, style=filled, fillcolor=lightblue];")
dot_lines.append("  overlap=false;")  # Prevent node overlap
dot_lines.append("  splines=ortho;")  # Orthogonal edges for clarity
for node in G.nodes():
    sanitized_node = sanitize_name(node)
    dot_lines.append(f'  "{sanitized_node}" [label="{sanitized_node}"];')
for edge in G.edges():
    sanitized_node1 = sanitize_name(edge[0])
    sanitized_node2 = sanitize_name(edge[1])
    dot_lines.append(f'  "{sanitized_node1}" -- "{sanitized_node2}";')

# Add subgraphs for visual separation if multiple components
if len(components) > 1:
    for i, component in enumerate(components, 1):
        dot_lines.append(f"  subgraph cluster_{i} {{")
        dot_lines.append(f'    label="Component {i}";')
        dot_lines.append(f"    color=blue;")
        for node in component:
            sanitized_node = sanitize_name(node)
            dot_lines.append(f'    "{sanitized_node}";')
        dot_lines.append("  }")
dot_lines.append("}")

# Write to friends_network_full.dot
dot_content = "\n".join(dot_lines)
with open("friends_network_full.dot", "w", encoding="utf-8") as f:
    f.write(dot_content)
print("DOT file 'friends_network_full.dot' has been generated for the entire graph.")

# Generate DOT file for the selected component
selected_G = G.subgraph(selected_component)
dot_lines = ["graph G {"]
dot_lines.append("  rankdir=LR;")
dot_lines.append("  node [shape=circle, style=filled, fillcolor=lightblue];")
dot_lines.append("  overlap=false;")
dot_lines.append("  splines=ortho;")
for node in selected_G.nodes():
    sanitized_node = sanitize_name(node)
    dot_lines.append(f'  "{sanitized_node}" [label="{sanitized_node}"];')
for edge in selected_G.edges():
    sanitized_node1 = sanitize_name(edge[0])
    sanitized_node2 = sanitize_name(edge[1])
    dot_lines.append(f'  "{sanitized_node1}" -- "{sanitized_node2}";')
dot_lines.append("}")

# Write to friends_network_selected.dot
dot_content = "\n".join(dot_lines)
with open("friends_network_selected.dot", "w", encoding="utf-8") as f:
    f.write(dot_content)
print(f"DOT file 'friends_network_selected.dot' has been generated for the component containing {user_input or 'the largest component'}.")

# Generate visualization for selected component
try:
    subprocess.run([
        "dot", f"-T{file_type}", "-Gcharset=utf-8",
        "friends_network_selected.dot", "-o", f"friends_network_selected.{file_type}"
    ], check=True)
    print(f"Generated 'friends_network_selected.{file_type}' for the selected component.")
except subprocess.CalledProcessError:
    print(f"Error: Failed to generate 'friends_network_selected.{file_type}'. Ensure Graphviz is installed and 'dot' is in your PATH.")
except FileNotFoundError:
    print("Error: Graphviz 'dot' command not found. Please install Graphviz or check your PATH.")

# Generate interactive HTML visualization with full graph data and click-to-highlight/search functionality
html_content = """<!DOCTYPE html>
<html>
<head>
    <title>Friends Network Interactive Visualization</title>
    <script type="text/javascript" src="https://unpkg.com/vis-network/standalone/umd/vis-network.min.js"></script>
    <style>
        body {{ font-family: sans-serif; }}
        #network {{
            width: 100%;
            height: 600px;
            border: 1px solid lightgray;
        }}
        #controls {{
            margin-bottom: 10px;
        }}
    </style>
</head>
<body>
    <h2>Friends Network Interactive Visualization</h2>
    <div id="controls">
        <label for="searchBox">Find Person:</label>
        <input type="text" id="searchBox" placeholder="Enter a name">
        <button onclick="findPerson()">Search</button>
        <button onclick="resetNetwork()">Reset View</button>
    </div>
    <p>Click a node to highlight its immediate connections. Use your mouse to zoom/pan or drag nodes to rearrange them.</p>
    <div id="network"></div>
    <script>
        // Store the full graph data
        var all_nodes_data = new vis.DataSet([
            {nodes_data}
        ]);
        var all_edges_data = new vis.DataSet([
            {edges_data}
        ]);

        var container = document.getElementById('network');
        var network;

        function createNetwork(nodesData, edgesData) {{
            var data = {{
                nodes: nodesData,
                edges: edgesData
            }};
            var options = {{
                nodes: {{
                    shape: 'dot',
                    font: {{ size: 12 }},
                    color: {{ background: 'lightblue', border: 'blue' }}
                }},
                edges: {{ smooth: {{ type: 'continuous' }} }},
                physics: {{ enabled: true }},
                interaction: {{ hover: true, zoomView: true, dragView: true }}
            }};
            
            network = new vis.Network(container, data, options);

            // Add the click event listener
            network.on('click', function(params) {{
                if (params.nodes.length > 0) {{
                    var nodeId = params.nodes[0];
                    highlightConnections(nodeId);
                }}
            }});
        }}

        function highlightConnections(centerNodeId) {{
            // Reset all colors
            var allNodes = all_nodes_data.get();
            var updatedNodes = allNodes.map(node => {{
                return {{...node, color: {{ background: 'lightblue', border: 'blue' }} }};
            }});
            all_nodes_data.update(updatedNodes);

            var allEdges = all_edges_data.get();
            var updatedEdges = allEdges.map(edge => {{
                return {{...edge, color: 'lightgray' }};
            }});
            all_edges_data.update(updatedEdges);
            
            // Highlight the center node and its connections
            var connectedEdges = all_edges_data.get({{'filter': function(item) {{
                return item.from === centerNodeId || item.to === centerNodeId;
            }}}});

            var connectedNodeIds = new Set();
            connectedNodeIds.add(centerNodeId);
            connectedEdges.forEach(function(edge) {{
                connectedNodeIds.add(edge.from);
                connectedNodeIds.add(edge.to);
            }});

            var nodesToHighlight = Array.from(connectedNodeIds).map(id => {{
                return {{id: id, color: {{ background: 'lightgreen', border: 'darkgreen' }} }};
            }});
            all_nodes_data.update(nodesToHighlight);

            var edgesToHighlight = connectedEdges.map(edge => {{
                return {{id: edge.id, color: 'darkgreen', width: 2 }};
            }});
            all_edges_data.update(edgesToHighlight);

            // Move the camera to the selected node
            network.focus(centerNodeId, {{ scale: 1.5, animation: true }});
        }}

        function findPerson() {{
            var searchName = document.getElementById('searchBox').value.trim();
            if (searchName === '') return;

            var foundNode = all_nodes_data.get({{'filter': function(item) {{
                return item.label.toLowerCase() === searchName.toLowerCase();
            }}}})[0];
            
            if (foundNode) {{
                // Only highlight and focus if the found node is not already centered
                if (network.getScale() !== 1.5 || network.getCenter().x !== foundNode.x || network.getCenter().y !== foundNode.y) {{
                    highlightConnections(foundNode.id);
                }}
            }} else {{
                alert('Person not found in the network.');
            }}
        }}

        function resetNetwork() {{
            createNetwork(all_nodes_data, all_edges_data);
        }}

        // Initialize with the full graph
        createNetwork(all_nodes_data, all_edges_data);
    </script>
</body>
</html>
"""

# Prepare nodes and edges for vis.js using the FULL graph
nodes_data = []
node_id_map = {node: i for i, node in enumerate(G.nodes())}
for node, i in node_id_map.items():
    sanitized_node = sanitize_name(node)
    nodes_data.append(f'{{id: {i}, label: "{sanitized_node}"}}')
nodes_data_str = ",\n            ".join(nodes_data)

edges_data = []
# Give each edge a unique ID for highlighting
edge_id_counter = 0
for edge in G.edges():
    id1 = node_id_map[edge[0]]
    id2 = node_id_map[edge[1]]
    edges_data.append(f'{{id: {edge_id_counter}, from: {id1}, to: {id2}}}')
    edge_id_counter += 1
edges_data_str = ",\n            ".join(edges_data)

# Write interactive HTML file
html_output_filename = "friends_network_interactive.html"
html_output = html_content.format(nodes_data=nodes_data_str, edges_data=edges_data_str)
with open(html_output_filename, "w", encoding="utf-8") as f:
    f.write(html_output)
print(f"Interactive visualization '{html_output_filename}' has been generated.")