"""
Graph Visualization Generator for Bengal SSG.

Generates interactive D3.js visualizations of the site's knowledge graph.
Inspired by Obsidian's graph view.
"""


from __future__ import annotations

import json
from dataclasses import asdict, dataclass
from typing import TYPE_CHECKING, Any

from bengal.utils.logger import get_logger

if TYPE_CHECKING:
    from bengal.analysis.knowledge_graph import KnowledgeGraph
    from bengal.core.site import Site

logger = get_logger(__name__)


@dataclass
class GraphNode:
    """
    Node in the graph visualization.

    Attributes:
        id: Unique identifier for the node
        label: Display label (page title)
        url: URL to the page
        type: Page type (page, index, generated, etc.)
        tags: List of tags
        incoming_refs: Number of incoming references
        outgoing_refs: Number of outgoing references
        connectivity: Total connectivity score
        size: Visual size (based on connectivity)
        color: Node color (based on type or connectivity)
    """

    id: str
    label: str
    url: str
    type: str
    tags: list[str]
    incoming_refs: int
    outgoing_refs: int
    connectivity: int
    size: int
    color: str


@dataclass
class GraphEdge:
    """
    Edge in the graph visualization.

    Attributes:
        source: Source node ID
        target: Target node ID
        weight: Edge weight (link strength)
    """

    source: str
    target: str
    weight: int = 1


class GraphVisualizer:
    """
    Generate interactive D3.js visualizations of knowledge graphs.

    Creates standalone HTML files with:
    - Force-directed graph layout
    - Interactive node exploration
    - Search and filtering
    - Responsive design
    - Customizable styling

    Example:
        >>> visualizer = GraphVisualizer(site, graph)
        >>> html = visualizer.generate_html()
        >>> Path('graph.html').write_text(html)
    """

    def __init__(self, site: Site, graph: KnowledgeGraph):
        """
        Initialize graph visualizer.

        Args:
            site: Site instance
            graph: Built KnowledgeGraph instance
        """
        self.site = site
        self.graph = graph

        if not graph._built:
            raise ValueError("KnowledgeGraph must be built before visualization")

    def generate_graph_data(self) -> dict[str, Any]:
        """
        Generate D3.js-compatible graph data.

        Returns:
            Dictionary with 'nodes' and 'edges' arrays
        """
        logger.info("graph_viz_generate_start", total_pages=len(self.site.pages))

        nodes = []
        edges = []

        # Generate nodes
        for page in self.site.pages:
            page_id = str(id(page))
            connectivity = self.graph.get_connectivity(page)

            # Determine node color based on type or connectivity
            color = self._get_node_color(page, connectivity)

            # Calculate visual size (min 10, max 50)
            size = min(50, 10 + connectivity.connectivity_score * 2)

            node = GraphNode(
                id=page_id,
                label=page.title or "Untitled",
                url=page.url if hasattr(page, "url") else "#",
                type=page.metadata.get("type", "page"),
                tags=list(page.tags) if hasattr(page, "tags") else [],
                incoming_refs=connectivity.incoming_refs,
                outgoing_refs=connectivity.outgoing_refs,
                connectivity=connectivity.connectivity_score,
                size=size,
                color=color,
            )

            nodes.append(asdict(node))

        # Generate edges
        for page in self.site.pages:
            source_id = str(id(page))

            # Get outgoing references
            target_ids = self.graph.outgoing_refs.get(id(page), set())
            for target_id in target_ids:
                edges.append(asdict(GraphEdge(source=source_id, target=str(target_id), weight=1)))

        logger.info("graph_viz_generate_complete", nodes=len(nodes), edges=len(edges))

        return {
            "nodes": nodes,
            "edges": edges,
            "stats": {
                "total_pages": len(nodes),
                "total_links": len(edges),
                "hubs": self.graph.metrics.hub_count,
                "orphans": self.graph.metrics.orphan_count,
            },
        }

    def _get_node_color(self, page: Any, connectivity: Any) -> str:
        """
        Determine node color based on page properties.

        Args:
            page: Page object
            connectivity: PageConnectivity object

        Returns:
            Hex color code
        """
        # Color by connectivity level
        if connectivity.is_orphan:
            return "#ef4444"  # Red for orphans
        elif connectivity.is_hub:
            return "#3b82f6"  # Blue for hubs
        elif page.metadata.get("_generated"):
            return "#8b5cf6"  # Purple for generated pages
        else:
            return "#6b7280"  # Gray for regular pages

    def generate_html(self, title: str | None = None) -> str:
        """
        Generate complete standalone HTML visualization.

        Args:
            title: Page title (defaults to site title)

        Returns:
            Complete HTML document as string
        """
        graph_data = self.generate_graph_data()

        if title is None:
            title = f"Knowledge Graph - {self.site.config.get('title', 'Site')}"

        html = f"""<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>{title}</title>
    <script src="https://d3js.org/d3.v7.min.js"></script>
    <style>
        * {{
            margin: 0;
            padding: 0;
            box-sizing: border-box;
        }}

        body {{
            font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
            overflow: hidden;
            background: #0a0a0a;
            color: #e5e7eb;
        }}

        #container {{
            width: 100vw;
            height: 100vh;
            position: relative;
        }}

        #graph {{
            width: 100%;
            height: 100%;
        }}

        .controls {{
            position: absolute;
            top: 20px;
            left: 20px;
            background: rgba(17, 24, 39, 0.95);
            border: 1px solid #374151;
            border-radius: 8px;
            padding: 16px;
            max-width: 300px;
            box-shadow: 0 4px 6px rgba(0, 0, 0, 0.3);
        }}

        .controls h2 {{
            font-size: 18px;
            margin-bottom: 12px;
            color: #f3f4f6;
        }}

        .controls input {{
            width: 100%;
            padding: 8px 12px;
            border: 1px solid #4b5563;
            border-radius: 4px;
            background: #1f2937;
            color: #e5e7eb;
            font-size: 14px;
            margin-bottom: 12px;
        }}

        .controls input:focus {{
            outline: none;
            border-color: #3b82f6;
        }}

        .stats {{
            font-size: 13px;
            color: #9ca3af;
            line-height: 1.6;
        }}

        .stats strong {{
            color: #e5e7eb;
        }}

        .legend {{
            position: absolute;
            bottom: 20px;
            left: 20px;
            background: rgba(17, 24, 39, 0.95);
            border: 1px solid #374151;
            border-radius: 8px;
            padding: 16px;
            box-shadow: 0 4px 6px rgba(0, 0, 0, 0.3);
        }}

        .legend h3 {{
            font-size: 14px;
            margin-bottom: 8px;
            color: #f3f4f6;
        }}

        .legend-item {{
            display: flex;
            align-items: center;
            margin: 6px 0;
            font-size: 12px;
            color: #9ca3af;
        }}

        .legend-color {{
            width: 16px;
            height: 16px;
            border-radius: 50%;
            margin-right: 8px;
        }}

        .node {{
            cursor: pointer;
            stroke: rgba(255, 255, 255, 0.3);
            stroke-width: 1.5px;
        }}

        .node:hover {{
            stroke: #fff;
            stroke-width: 3px;
        }}

        .node.highlighted {{
            stroke: #fbbf24;
            stroke-width: 3px;
        }}

        .link {{
            stroke: rgba(156, 163, 175, 0.3);
            stroke-width: 1px;
        }}

        .link.highlighted {{
            stroke: rgba(251, 191, 36, 0.8);
            stroke-width: 2px;
        }}

        .label {{
            font-size: 11px;
            fill: #d1d5db;
            pointer-events: none;
            text-anchor: middle;
        }}

        .tooltip {{
            position: absolute;
            background: rgba(17, 24, 39, 0.98);
            border: 1px solid #374151;
            border-radius: 6px;
            padding: 12px;
            pointer-events: none;
            font-size: 13px;
            max-width: 250px;
            box-shadow: 0 4px 6px rgba(0, 0, 0, 0.3);
            display: none;
            z-index: 1000;
        }}

        .tooltip h4 {{
            margin-bottom: 6px;
            color: #f3f4f6;
            font-size: 14px;
        }}

        .tooltip p {{
            margin: 4px 0;
            color: #9ca3af;
            line-height: 1.4;
        }}

        .tooltip .tags {{
            display: flex;
            flex-wrap: wrap;
            gap: 4px;
            margin-top: 6px;
        }}

        .tooltip .tag {{
            background: #374151;
            padding: 2px 6px;
            border-radius: 3px;
            font-size: 11px;
            color: #d1d5db;
        }}
    </style>
</head>
<body>
    <div id="container">
        <div class="controls">
            <h2>🗺️ Knowledge Graph</h2>
            <input
                type="text"
                id="search"
                placeholder="Search pages..."
                autocomplete="off"
            />
            <div class="stats">
                <p><strong>Pages:</strong> {graph_data["stats"]["total_pages"]}</p>
                <p><strong>Links:</strong> {graph_data["stats"]["total_links"]}</p>
                <p><strong>Hubs:</strong> {graph_data["stats"]["hubs"]}</p>
                <p><strong>Orphans:</strong> {graph_data["stats"]["orphans"]}</p>
            </div>
        </div>

        <div class="legend">
            <h3>Legend</h3>
            <div class="legend-item">
                <div class="legend-color" style="background: #3b82f6;"></div>
                <span>Hub (highly connected)</span>
            </div>
            <div class="legend-item">
                <div class="legend-color" style="background: #6b7280;"></div>
                <span>Regular page</span>
            </div>
            <div class="legend-item">
                <div class="legend-color" style="background: #ef4444;"></div>
                <span>Orphan (no incoming links)</span>
            </div>
            <div class="legend-item">
                <div class="legend-color" style="background: #8b5cf6;"></div>
                <span>Generated page</span>
            </div>
        </div>

        <div id="graph"></div>
        <div class="tooltip" id="tooltip"></div>
    </div>

    <script>
        // Graph data
        const graphData = {json.dumps(graph_data, indent=2)};

        // Dimensions
        const width = window.innerWidth;
        const height = window.innerHeight;

        // Create SVG
        const svg = d3.select("#graph")
            .append("svg")
            .attr("width", width)
            .attr("height", height);

        // Add zoom behavior
        const g = svg.append("g");

        svg.call(d3.zoom()
            .scaleExtent([0.1, 4])
            .on("zoom", (event) => {{
                g.attr("transform", event.transform);
            }})
        );

        // Create force simulation
        const simulation = d3.forceSimulation(graphData.nodes)
            .force("link", d3.forceLink(graphData.edges)
                .id(d => d.id)
                .distance(50))
            .force("charge", d3.forceManyBody()
                .strength(-200))
            .force("center", d3.forceCenter(width / 2, height / 2))
            .force("collision", d3.forceCollide()
                .radius(d => d.size + 5));

        // Render links
        const link = g.append("g")
            .selectAll("line")
            .data(graphData.edges)
            .enter().append("line")
            .attr("class", "link");

        // Render nodes
        const node = g.append("g")
            .selectAll("circle")
            .data(graphData.nodes)
            .enter().append("circle")
            .attr("class", "node")
            .attr("r", d => d.size)
            .attr("fill", d => d.color)
            .call(d3.drag()
                .on("start", dragstarted)
                .on("drag", dragged)
                .on("end", dragended))
            .on("click", (event, d) => {{
                window.location.href = d.url;
            }})
            .on("mouseover", (event, d) => {{
                showTooltip(event, d);
                highlightConnections(d);
            }})
            .on("mouseout", () => {{
                hideTooltip();
                clearHighlights();
            }});

        // Update positions on simulation tick
        simulation.on("tick", () => {{
            link
                .attr("x1", d => d.source.x)
                .attr("y1", d => d.source.y)
                .attr("x2", d => d.target.x)
                .attr("y2", d => d.target.y);

            node
                .attr("cx", d => d.x)
                .attr("cy", d => d.y);
        }});

        // Drag functions
        function dragstarted(event, d) {{
            if (!event.active) simulation.alphaTarget(0.3).restart();
            d.fx = d.x;
            d.fy = d.y;
        }}

        function dragged(event, d) {{
            d.fx = event.x;
            d.fy = event.y;
        }}

        function dragended(event, d) {{
            if (!event.active) simulation.alphaTarget(0);
            d.fx = null;
            d.fy = null;
        }}

        // Tooltip functions
        const tooltip = d3.select("#tooltip");

        function showTooltip(event, d) {{
            const tags = d.tags.length > 0
                ? `<div class="tags">${{d.tags.map(t => `<span class="tag">${{t}}</span>`).join('')}}</div>`
                : '';

            tooltip
                .style("display", "block")
                .style("left", (event.pageX + 10) + "px")
                .style("top", (event.pageY + 10) + "px")
                .html(`
                    <h4>${{d.label}}</h4>
                    <p>Type: ${{d.type}}</p>
                    <p>Incoming: ${{d.incoming_refs}} | Outgoing: ${{d.outgoing_refs}}</p>
                    ${{tags}}
                `);
        }}

        function hideTooltip() {{
            tooltip.style("display", "none");
        }}

        // Highlight connections
        function highlightConnections(d) {{
            // Highlight connected nodes
            const connectedNodeIds = new Set();
            connectedNodeIds.add(d.id);

            graphData.edges.forEach(e => {{
                if (e.source.id === d.id || e.source === d.id) {{
                    connectedNodeIds.add(typeof e.target === 'object' ? e.target.id : e.target);
                }}
                if (e.target.id === d.id || e.target === d.id) {{
                    connectedNodeIds.add(typeof e.source === 'object' ? e.source.id : e.source);
                }}
            }});

            node.classed("highlighted", n => connectedNodeIds.has(n.id));

            // Highlight connected links
            link.classed("highlighted", e => {{
                const sourceId = typeof e.source === 'object' ? e.source.id : e.source;
                const targetId = typeof e.target === 'object' ? e.target.id : e.target;
                return sourceId === d.id || targetId === d.id;
            }});
        }}

        function clearHighlights() {{
            node.classed("highlighted", false);
            link.classed("highlighted", false);
        }}

        // Search functionality
        const searchInput = document.getElementById('search');
        searchInput.addEventListener('input', (e) => {{
            const query = e.target.value.toLowerCase();

            if (query) {{
                node.style("opacity", d => {{
                    return d.label.toLowerCase().includes(query) ||
                           d.tags.some(t => t.toLowerCase().includes(query))
                        ? 1 : 0.2;
                }});

                link.style("opacity", 0.1);
            }} else {{
                node.style("opacity", 1);
                link.style("opacity", 1);
            }}
        }});

        // Keyboard shortcuts
        document.addEventListener('keydown', (e) => {{
            if (e.key === '/' || (e.metaKey && e.key === 'k')) {{
                e.preventDefault();
                searchInput.focus();
            }}
            if (e.key === 'Escape') {{
                searchInput.value = '';
                searchInput.blur();
                node.style("opacity", 1);
                link.style("opacity", 1);
            }}
        }});
    </script>
</body>
</html>
"""

        return html
