"""Generate a self-contained HTML dashboard for exploring the knowledge base."""

from __future__ import annotations

import json
import html
import logging
import webbrowser
from pathlib import Path
from typing import Optional

from res_sum.graph.knowledge_graph import KnowledgeGraph
from res_sum.retrieval.vector_store import VectorStore

logger = logging.getLogger("res_sum")


def generate_dashboard(
    knowledge_graph: KnowledgeGraph,
    vector_store: VectorStore,
    communities: Optional[dict[str, int]] = None,
    community_summaries: Optional[dict[int, str]] = None,
    output_path: str | Path = "./explore_dashboard.html",
    open_browser: bool = True,
    title: str = "res-sum Knowledge Base Explorer",
) -> Path:
    """Generate an interactive HTML dashboard and optionally open it.

    Args:
        knowledge_graph: The knowledge graph to visualize.
        vector_store: The vector store to inspect.
        communities: Entity → community ID mapping.
        community_summaries: Community ID → summary text.
        output_path: Where to save the HTML file.
        open_browser: If True, opens the dashboard in the default browser.
        title: Dashboard title.

    Returns:
        Path to the generated HTML file.
    """
    output_path = Path(output_path)
    output_path.parent.mkdir(parents=True, exist_ok=True)

    communities = communities or {}
    community_summaries = community_summaries or {}

    # Gather data
    overview_data = _build_overview_data(knowledge_graph, vector_store, communities)
    graph_data = _build_graph_data(knowledge_graph, communities)
    vector_data = _build_vector_data(vector_store)
    community_data = _build_community_data(communities, community_summaries, knowledge_graph)

    # Generate HTML
    html_content = _render_html(
        title=title,
        overview_data=overview_data,
        graph_data=graph_data,
        vector_data=vector_data,
        community_data=community_data,
    )

    output_path.write_text(html_content, encoding="utf-8")
    logger.info(f"Dashboard saved to {output_path}")

    if open_browser:
        webbrowser.open(f"file://{output_path.resolve()}")

    return output_path


# ---------------------------------------------------------------------------
# Data builders
# ---------------------------------------------------------------------------

def _build_overview_data(
    kg: KnowledgeGraph, vs: VectorStore, communities: dict
) -> dict:
    summary = kg.summary() if kg.num_nodes() > 0 else {}
    return {
        "total_chunks": vs.count(),
        "papers": vs.list_papers() if hasattr(vs, "list_papers") else [],
        "graph_nodes": kg.num_nodes(),
        "graph_edges": kg.num_edges(),
        "entity_types": summary.get("entity_types", {}),
        "num_communities": len(set(communities.values())) if communities else 0,
        "connected_components": summary.get("connected_components", 0),
    }


def _build_graph_data(kg: KnowledgeGraph, communities: dict) -> dict:
    """Build nodes and edges for vis.js visualization."""
    if kg.num_nodes() == 0:
        return {"nodes": [], "edges": []}

    # Color map and hierarchy levels for node types
    type_colors = {
        "PAPER": "#1a1a2e",
        "RESEARCH_PROBLEM": "#e74c3c",
        "METHOD": "#f39c12",
        "STUDY_SYSTEM": "#9b59b6",
        "FINDING": "#2ecc71",
        "CLAIM": "#3498db",
        "LIMITATION": "#e67e22",
        # Legacy types (backward compat)
        "SPECIES": "#2ecc71",
        "LOCATION": "#3498db",
        "METRIC": "#f39c12",
        "CONCEPT": "#9b59b6",
        "TEMPORAL": "#1abc9c",
        "ENTITY": "#95a5a6",
    }

    # Hierarchy levels: lower = higher in tree
    type_levels = {
        "PAPER": 0,
        "RESEARCH_PROBLEM": 1,
        "METHOD": 2,
        "STUDY_SYSTEM": 2,
        "FINDING": 3,
        "CLAIM": 4,
        "LIMITATION": 4,
    }

    # Compute centrality scores
    import networkx as nx_lib
    centrality = nx_lib.degree_centrality(kg.graph) if kg.graph.number_of_nodes() > 0 else {}

    nodes = []
    for node, data in kg.graph.nodes(data=True):
        etype = data.get("node_type", "") or data.get("entity_type", "FINDING")
        color = type_colors.get(etype, "#95a5a6")
        comm = communities.get(node, -1)
        description = data.get("description", "")
        evidence = data.get("evidence", "")
        confidence = data.get("confidence", "")
        papers = data.get("papers", "").replace("|", ", ")
        cent_score = centrality.get(node, 0)

        # Size by degree
        degree = kg.graph.degree(node)
        size = max(10, min(50, 10 + degree * 3))

        # Build tooltip with research content
        tooltip = f"<b>{html.escape(node)}</b><br>Type: {etype}<br>"
        if description:
            tooltip += f"<br><i>{html.escape(description[:300])}</i><br>"
        if evidence:
            tooltip += f"<br>Evidence: {html.escape(evidence[:200])}<br>"
        if confidence:
            tooltip += f"Confidence: {confidence}<br>"
        tooltip += f"<br>Centrality: {cent_score:.3f}<br>"
        tooltip += f"Community: {comm}<br>"
        tooltip += f"Papers: {html.escape(papers) if papers else 'unknown'}<br>"
        tooltip += f"Connections: {degree}"

        # Assign hierarchy level
        level = type_levels.get(etype, 3)

        # Shape by type
        shape = "diamond" if etype == "PAPER" else "dot"

        nodes.append({
            "id": node,
            "label": node,
            "color": color,
            "size": size,
            "shape": shape,
            "level": level,
            "title": tooltip,
            "group": etype,
            "centrality": round(cent_score, 4),
            "degree": degree,
            "description": description[:200],
        })

    edges = []
    for u, v, data in kg.graph.edges(data=True):
        rel_type = data.get("relation_type", "")
        evidence = data.get("evidence", "")
        edges.append({
            "from": u,
            "to": v,
            "label": rel_type,
            "title": (
                f"<b>{html.escape(rel_type)}</b><br>"
                f"{html.escape(u)} → {html.escape(v)}<br>"
                f"Evidence: {html.escape(evidence[:200]) if evidence else 'none'}"
            ),
            "arrows": "to",
            "color": {"color": "#cccccc", "highlight": "#ff6600"},
        })

    return {"nodes": nodes, "edges": edges}


def _build_vector_data(vs: VectorStore) -> list[dict]:
    """Build chunk data for the vector store browser."""
    papers = []
    try:
        all_data = vs.collection.get(include=["metadatas", "documents"])
        paper_chunks: dict[str, list] = {}

        for i in range(len(all_data.get("ids", []))):
            meta = all_data["metadatas"][i] if all_data.get("metadatas") else {}
            doc = all_data["documents"][i] if all_data.get("documents") else ""
            paper_name = meta.get("paper_filename", "unknown")

            if paper_name not in paper_chunks:
                paper_chunks[paper_name] = []

            paper_chunks[paper_name].append({
                "id": all_data["ids"][i],
                "text_preview": doc[:300] + "..." if len(doc) > 300 else doc,
                "section": meta.get("section", "unknown"),
                "chunk_index": meta.get("chunk_index", -1),
            })

        for paper_name, chunks in paper_chunks.items():
            chunks.sort(key=lambda x: x["chunk_index"])
            papers.append({
                "paper": paper_name,
                "num_chunks": len(chunks),
                "chunks": chunks,
            })
    except Exception as e:
        logger.warning(f"Failed to build vector data: {e}")

    return papers


def _build_community_data(
    communities: dict, summaries: dict, kg: KnowledgeGraph
) -> list[dict]:
    """Build community info."""
    if not communities:
        return []

    comm_groups: dict[int, list[str]] = {}
    for entity, comm_id in communities.items():
        comm_groups.setdefault(comm_id, []).append(entity)

    result = []
    for comm_id, members in sorted(comm_groups.items()):
        result.append({
            "id": comm_id,
            "num_members": len(members),
            "members": members[:20],  # Cap for display
            "summary": summaries.get(comm_id, "No summary available"),
        })

    return result


# ---------------------------------------------------------------------------
# HTML renderer
# ---------------------------------------------------------------------------

def _render_html(
    title: str,
    overview_data: dict,
    graph_data: dict,
    vector_data: list,
    community_data: list,
) -> str:
    """Render the full dashboard HTML."""

    graph_json = json.dumps(graph_data, ensure_ascii=False)
    vector_json = json.dumps(vector_data, ensure_ascii=False)
    community_json = json.dumps(community_data, ensure_ascii=False)
    overview_json = json.dumps(overview_data, ensure_ascii=False)

    # Build entity type legend using the actual node type colors
    legend_colors = {
        "PAPER": "#1a1a2e",
        "RESEARCH_PROBLEM": "#e74c3c",
        "METHOD": "#f39c12",
        "STUDY_SYSTEM": "#9b59b6",
        "FINDING": "#2ecc71",
        "CLAIM": "#3498db",
        "LIMITATION": "#e67e22",
    }
    # Show all types that exist in the graph data
    active_types = set(overview_data.get("entity_types", {}).keys())
    active_types.add("PAPER")  # Always show PAPER in legend
    legend_html = " ".join(
        f'<span style="display:inline-flex;align-items:center;margin:0 10px;"><span style="display:inline-block;width:14px;height:14px;border-radius:50%;background:{color};margin-right:5px;border:1px solid rgba(0,0,0,0.15);"></span><b>{etype.replace("_"," ").title()}</b></span>'
        for etype, color in legend_colors.items()
        if etype in active_types
    )

    return f"""<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{html.escape(title)}</title>
<script src="https://unpkg.com/vis-network/standalone/umd/vis-network.min.js"></script>
<style>
* {{ margin: 0; padding: 0; box-sizing: border-box; }}
body {{ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; background: #f5f7fa; color: #333; }}
.header {{ background: #1a1a2e; color: white; padding: 20px 30px; }}
.header h1 {{ font-size: 22px; font-weight: 600; }}
.header p {{ font-size: 13px; color: #aaa; margin-top: 4px; }}
.tabs {{ display: flex; background: #16213e; padding: 0 20px; }}
.tab {{ padding: 12px 24px; color: #aaa; cursor: pointer; border-bottom: 3px solid transparent; font-size: 14px; }}
.tab:hover {{ color: white; }}
.tab.active {{ color: white; border-bottom-color: #e94560; }}
.content {{ display: none; padding: 24px; max-width: 1400px; margin: 0 auto; }}
.content.active {{ display: block; }}
.cards {{ display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 16px; margin-bottom: 24px; }}
.card {{ background: white; border-radius: 8px; padding: 20px; box-shadow: 0 1px 3px rgba(0,0,0,0.1); }}
.card .number {{ font-size: 32px; font-weight: 700; color: #1a1a2e; }}
.card .label {{ font-size: 13px; color: #888; margin-top: 4px; }}
.section {{ background: white; border-radius: 8px; padding: 20px; box-shadow: 0 1px 3px rgba(0,0,0,0.1); margin-bottom: 16px; }}
.section h3 {{ font-size: 16px; margin-bottom: 12px; color: #1a1a2e; }}
#graph-container {{ width: 100%; height: 1200px; border: 1px solid #ddd; border-radius: 8px; background: #fafbfc; }}
.legend {{ padding: 10px 0; font-size: 13px; }}
table {{ width: 100%; border-collapse: collapse; font-size: 13px; }}
th {{ text-align: left; padding: 8px 12px; background: #f8f9fa; border-bottom: 2px solid #dee2e6; }}
td {{ padding: 8px 12px; border-bottom: 1px solid #eee; }}
tr:hover {{ background: #f8f9fa; }}
.chunk-text {{ font-size: 12px; color: #555; max-height: 80px; overflow: hidden; cursor: pointer; }}
.chunk-text.expanded {{ max-height: none; }}
.badge {{ display: inline-block; padding: 2px 8px; border-radius: 12px; font-size: 11px; font-weight: 600; }}
.badge-section {{ background: #e8f4fd; color: #1a73e8; }}
.community-card {{ background: white; border-radius: 8px; padding: 16px; margin-bottom: 12px; box-shadow: 0 1px 3px rgba(0,0,0,0.1); }}
.community-card h4 {{ font-size: 14px; color: #1a1a2e; }}
.community-card p {{ font-size: 13px; color: #555; margin: 8px 0; }}
.members {{ font-size: 12px; color: #888; }}
.filter-bar {{ margin-bottom: 16px; }}
.filter-bar select, .filter-bar input {{ padding: 8px 12px; border: 1px solid #ddd; border-radius: 6px; font-size: 13px; margin-right: 8px; }}
</style>
</head>
<body>

<div class="header">
    <h1>{html.escape(title)}</h1>
    <p>Generated by res-sum &mdash; interactive exploration of your knowledge base</p>
</div>

<div class="tabs">
    <div class="tab active" onclick="switchTab('overview')">Overview</div>
    <div class="tab" onclick="switchTab('graph')">Knowledge Graph</div>
    <div class="tab" onclick="switchTab('vectors')">Vector Store</div>
    <div class="tab" onclick="switchTab('communities')">Communities</div>
</div>

<!-- OVERVIEW TAB -->
<div id="overview" class="content active">
    <div class="cards" id="overview-cards"></div>
    <div class="section">
        <h3>Top Entities by Centrality Score</h3>
        <table id="top-entities-table">
            <thead><tr><th>Entity</th><th>Type</th><th>Centrality</th><th>Connections</th></tr></thead>
            <tbody></tbody>
        </table>
    </div>
    <div class="section">
        <h3>Ingested Papers</h3>
        <table id="papers-table">
            <thead><tr><th>Paper</th></tr></thead>
            <tbody></tbody>
        </table>
    </div>
    <div class="section">
        <h3>Entity Types</h3>
        <table id="entity-types-table">
            <thead><tr><th>Type</th><th>Count</th></tr></thead>
            <tbody></tbody>
        </table>
    </div>
</div>

<!-- KNOWLEDGE GRAPH TAB -->
<div id="graph" class="content">
    <div class="legend">{legend_html}</div>
    <div class="filter-bar">
        <select id="graph-filter" onchange="filterGraph()">
            <option value="all">All entity types</option>
        </select>
        <input type="text" id="graph-search" placeholder="Search entity..." onkeyup="searchGraph()" />
        <button onclick="if(network) network.fit({{animation:true}})" style="padding:8px 16px;border:1px solid #ddd;border-radius:6px;background:white;cursor:pointer;font-size:13px;">Fit to Screen</button>
    </div>
    <p style="font-size:12px;color:#888;margin-bottom:8px;">Scroll to zoom. Click + drag to pan. Click a node to see details below.</p>
    <div id="graph-container"></div>
    <div class="section" style="margin-top:16px;">
        <h3>Selected Entity Details</h3>
        <div id="entity-details"><p style="color:#888">Click a node in the graph above to see details.</p></div>
    </div>
</div>

<!-- VECTOR STORE TAB -->
<div id="vectors" class="content">
    <div class="filter-bar">
        <select id="paper-filter" onchange="filterPaper()">
            <option value="all">All papers</option>
        </select>
    </div>
    <div class="section">
        <h3>Chunks</h3>
        <table id="chunks-table">
            <thead><tr><th>#</th><th>Section</th><th>Text Preview</th><th>Chunk ID</th></tr></thead>
            <tbody></tbody>
        </table>
    </div>
</div>

<!-- COMMUNITIES TAB -->
<div id="communities" class="content">
    <div id="communities-container"></div>
</div>

<script>
// Data from Python
const overviewData = {overview_json};
const graphData = {graph_json};
const vectorData = {vector_json};
const communityData = {community_json};

let network = null;
let allNodes = graphData.nodes;
let allEdges = graphData.edges;

// --- Tab switching ---
function switchTab(tabId) {{
    document.querySelectorAll('.content').forEach(c => c.classList.remove('active'));
    document.querySelectorAll('.tab').forEach(t => t.classList.remove('active'));
    document.getElementById(tabId).classList.add('active');
    event.target.classList.add('active');
    if (tabId === 'graph' && !network) initGraph();
}}

// --- Overview ---
function initOverview() {{
    const cards = document.getElementById('overview-cards');
    const metrics = [
        {{ label: 'Papers', value: overviewData.papers.length }},
        {{ label: 'Chunks', value: overviewData.total_chunks }},
        {{ label: 'Graph Nodes', value: overviewData.graph_nodes }},
        {{ label: 'Graph Edges', value: overviewData.graph_edges }},
        {{ label: 'Communities', value: overviewData.num_communities }},
    ];
    cards.innerHTML = metrics.map(m =>
        `<div class="card"><div class="number">${{m.value}}</div><div class="label">${{m.label}}</div></div>`
    ).join('');

    // Top entities by centrality
    const topEntities = [...allNodes].sort((a,b) => (b.centrality||0) - (a.centrality||0)).slice(0, 15);
    const topBody = document.querySelector('#top-entities-table tbody');
    topBody.innerHTML = topEntities.map(n =>
        `<tr><td>${{n.id}}</td><td><span class="badge badge-section">${{n.group}}</span></td><td>${{(n.centrality||0).toFixed(4)}}</td><td>${{n.degree||0}}</td></tr>`
    ).join('');

    const papersBody = document.querySelector('#papers-table tbody');
    papersBody.innerHTML = overviewData.papers.map(p => `<tr><td>${{p}}</td></tr>`).join('');

    const typesBody = document.querySelector('#entity-types-table tbody');
    typesBody.innerHTML = Object.entries(overviewData.entity_types)
        .sort((a,b) => b[1]-a[1])
        .map(([t,c]) => `<tr><td>${{t}}</td><td>${{c}}</td></tr>`).join('');
}}

// --- Knowledge Graph ---
function initGraph() {{
    if (allNodes.length === 0) {{
        document.getElementById('graph-container').innerHTML = '<p style="padding:20px;color:#888;">No entities in knowledge graph.</p>';
        return;
    }}

    const container = document.getElementById('graph-container');
    const data = {{ nodes: new vis.DataSet(allNodes), edges: new vis.DataSet(allEdges) }};
    const options = {{
        layout: {{
            hierarchical: {{
                enabled: true,
                direction: 'LR',
                sortMethod: 'directed',
                levelSeparation: 400,
                nodeSpacing: 250,
                treeSpacing: 300,
                blockShifting: true,
                edgeMinimization: true,
                parentCentralization: true,
                shakeTowards: 'roots',
            }}
        }},
        physics: {{ enabled: false }},
        interaction: {{ hover: true, tooltipDelay: 100, navigationButtons: true, keyboard: true, zoomView: true }},
        edges: {{
            font: {{ size: 8, color: '#888', strokeWidth: 2, strokeColor: 'white', background: 'rgba(255,255,255,0.9)', align: 'middle' }},
            arrows: {{ to: {{ enabled: true, scaleFactor: 0.5 }} }},
            smooth: {{ type: 'cubicBezier', forceDirection: 'horizontal', roundness: 0.5 }},
            color: {{ color: '#ccc', highlight: '#e94560', opacity: 0.7 }},
            length: 250,
        }},
        nodes: {{
            font: {{ size: 10, face: 'system-ui', multi: 'html', vadjust: -2 }},
            borderWidth: 2,
            widthConstraint: {{ maximum: 280 }},
            margin: {{ top: 8, bottom: 8, left: 10, right: 10 }},
        }},
    }};
    network = new vis.Network(container, data, options);

    // Fit all nodes into view after layout stabilizes
    network.once('stabilized', function() {{
        network.fit({{ animation: {{ duration: 500, easingFunction: 'easeInOutQuad' }} }});
    }});

    network.on('click', function(params) {{
        if (params.nodes.length > 0) {{
            showEntityDetails(params.nodes[0]);
        }}
    }});

    // Populate filter dropdown
    const types = [...new Set(allNodes.map(n => n.group))].sort();
    const select = document.getElementById('graph-filter');
    types.forEach(t => {{
        const opt = document.createElement('option');
        opt.value = t; opt.textContent = t;
        select.appendChild(opt);
    }});
}}

function filterGraph() {{
    const type = document.getElementById('graph-filter').value;
    if (!network) return;
    const filtered = type === 'all' ? allNodes : allNodes.filter(n => n.group === type);
    const nodeIds = new Set(filtered.map(n => n.id));
    const filteredEdges = allEdges.filter(e => nodeIds.has(e.from) && nodeIds.has(e.to));
    network.setData({{ nodes: new vis.DataSet(filtered), edges: new vis.DataSet(filteredEdges) }});
}}

function searchGraph() {{
    const query = document.getElementById('graph-search').value.toLowerCase();
    if (!network || !query) return;
    const match = allNodes.find(n => n.id.toLowerCase().includes(query));
    if (match) {{
        network.selectNodes([match.id]);
        network.focus(match.id, {{ scale: 1.5, animation: true }});
        showEntityDetails(match.id);
    }}
}}

function showEntityDetails(nodeId) {{
    const node = allNodes.find(n => n.id === nodeId);
    const edges = allEdges.filter(e => e.from === nodeId || e.to === nodeId);
    if (!node) return;

    let html = `<table>`;
    html += `<tr><td><b>Name</b></td><td>${{nodeId}}</td></tr>`;
    html += `<tr><td><b>Type</b></td><td><span class="badge badge-section">${{node.group}}</span></td></tr>`;
    if (node.description) {{
        html += `<tr><td><b>Description</b></td><td style="font-style:italic">${{node.description}}</td></tr>`;
    }}
    html += `<tr><td><b>Centrality</b></td><td>${{(node.centrality||0).toFixed(4)}}</td></tr>`;
    html += `<tr><td><b>Connections</b></td><td>${{node.degree||edges.length}}</td></tr>`;
    html += `</table>`;

    if (edges.length > 0) {{
        html += `<h4 style="margin:12px 0 8px;">Relationships</h4><table>`;
        html += `<thead><tr><th>From</th><th>Relation</th><th>To</th></tr></thead><tbody>`;
        edges.forEach(e => {{
            html += `<tr><td>${{e.from}}</td><td>${{e.label}}</td><td>${{e.to}}</td></tr>`;
        }});
        html += `</tbody></table>`;
    }}

    document.getElementById('entity-details').innerHTML = html;
}}

// --- Vector Store ---
function initVectors() {{
    const select = document.getElementById('paper-filter');
    vectorData.forEach(p => {{
        const opt = document.createElement('option');
        opt.value = p.paper; opt.textContent = `${{p.paper}} (${{p.num_chunks}} chunks)`;
        select.appendChild(opt);
    }});
    renderChunks('all');
}}

function filterPaper() {{
    renderChunks(document.getElementById('paper-filter').value);
}}

function renderChunks(paperFilter) {{
    const tbody = document.querySelector('#chunks-table tbody');
    let allChunks = [];
    vectorData.forEach(p => {{
        if (paperFilter === 'all' || p.paper === paperFilter) {{
            p.chunks.forEach(c => allChunks.push({{ ...c, paper: p.paper }}));
        }}
    }});
    tbody.innerHTML = allChunks.map((c, i) =>
        `<tr>
            <td>${{c.chunk_index >= 0 ? c.chunk_index : i}}</td>
            <td><span class="badge badge-section">${{c.section}}</span></td>
            <td><div class="chunk-text" onclick="this.classList.toggle('expanded')">${{c.text_preview.replace(/</g, '&lt;')}}</div></td>
            <td style="font-size:11px;color:#aaa;">${{c.id.substring(0,8)}}...</td>
        </tr>`
    ).join('');
}}

// --- Communities ---
function initCommunities() {{
    const container = document.getElementById('communities-container');
    if (communityData.length === 0) {{
        container.innerHTML = '<p style="color:#888;">No communities detected.</p>';
        return;
    }}
    container.innerHTML = communityData.map(c => `
        <div class="community-card">
            <h4>Community ${{c.id}} &mdash; ${{c.num_members}} entities</h4>
            <p>${{c.summary}}</p>
            <div class="members">Members: ${{c.members.join(', ')}}${{c.num_members > 20 ? '...' : ''}}</div>
        </div>
    `).join('');
}}

// --- Init ---
initOverview();
initVectors();
initCommunities();
</script>
</body>
</html>"""
