"""HTML templates for static site generation.

Uses Jinja2 for templating with inline template definitions.
Templates include: base layout with 3-column grid, entry page, index page, and tag pages.
"""

from __future__ import annotations

from datetime import datetime
from typing import TYPE_CHECKING

from jinja2 import BaseLoader, Environment, select_autoescape

if TYPE_CHECKING:
    from ..models import RelationLink
    from .generator import EntryData


def _escape_html(text: str) -> str:
    """Escape HTML special characters."""
    return (
        text.replace("&", "&amp;").replace("<", "&lt;").replace(">", "&gt;").replace('"', "&quot;")
    )


def _format_date(value) -> str:
    """Format a datetime for display in templates.

    Args:
        value: datetime object or None

    Returns:
        Formatted date string like "Jan 6, 2026 at 2:30 PM" or empty string if None.
        If time is midnight (00:00:00), only shows date.
    """
    if value is None:
        return ""
    try:
        # Check if we have a meaningful time component (not midnight)
        has_time = value.hour != 0 or value.minute != 0 or value.second != 0
        if has_time:
            # Format as "Jan 6, 2026 at 2:30 PM"
            return value.strftime("%b %-d, %Y at %-I:%M %p")
        else:
            # Date only - "Jan 6, 2026"
            return value.strftime("%b %-d, %Y")
    except (AttributeError, ValueError):
        # Fall back for Windows (no %-d support)
        try:
            has_time = value.hour != 0 or value.minute != 0 or value.second != 0
            if has_time:
                result = value.strftime("%b %d, %Y at %I:%M %p")
                return result.replace(" 0", " ").replace(" 0", " ")  # Remove leading zeros
            else:
                return value.strftime("%b %d, %Y").replace(" 0", " ")
        except (AttributeError, ValueError):
            return str(value).split()[0] if value else ""


def _safe(html: str) -> str:
    """Mark HTML as safe for Jinja2 (won't be escaped)."""
    from markupsafe import Markup

    return Markup(html)


def _recent_sort_key(entry: EntryData) -> datetime:
    """Sort by most recent activity, preferring updated over created.

    Normalizes datetimes to handle mixed timezone-aware and timezone-naive
    values (comparing them would raise TypeError). Strips timezone info
    to get a consistent naive datetime for comparison.
    """
    dt = entry.metadata.updated or entry.metadata.created
    if dt is None:
        return datetime.min
    # Strip timezone info to allow comparison between aware and naive datetimes
    return dt.replace(tzinfo=None) if dt.tzinfo else dt


def _build_file_tree(entries: list[EntryData], current_path: str = "", base_url: str = "") -> str:
    """Build HTML for the sidebar file tree navigation.

    Args:
        entries: All entry data
        current_path: Currently viewed entry path (for highlighting)
        base_url: Base URL prefix for links (e.g., "/my-kb")

    Returns:
        HTML string for the tree structure
    """
    # Group entries by their top-level folder
    folders: dict[str, list[EntryData]] = {}
    root_entries: list[EntryData] = []

    for entry in sorted(entries, key=lambda e: e.path.lower()):
        parts = entry.path.split("/")
        if len(parts) > 1:
            folder = parts[0]
            if folder not in folders:
                folders[folder] = []
            folders[folder].append(entry)
        else:
            root_entries.append(entry)

    html_parts = ['<div class="tree" id="tree">']

    # Render folders
    for folder in sorted(folders.keys()):
        html_parts.append(f"""
            <div class="tree-folder">
                <div class="tree-folder-header">
                    <span class="tree-icon folder">📁</span>
                    <span class="tree-label">{_escape_html(folder)}</span>
                </div>
                <div class="tree-children">""")

        for entry in folders[folder]:
            active_class = " active" if entry.path == current_path else ""
            html_parts.append(f'''
                    <a href="{base_url}/{entry.path}.html" class="tree-item{active_class}">
                        <span class="tree-icon file">◇</span>
                        <span class="tree-label">{_escape_html(entry.title)}</span>
                    </a>''')

        html_parts.append("""
                </div>
            </div>""")

    # Render root entries
    for entry in root_entries:
        active_class = " active" if entry.path == current_path else ""
        html_parts.append(f'''
            <a href="{base_url}/{entry.path}.html" class="tree-item{active_class}">
                <span class="tree-icon file">◇</span>
                <span class="tree-label">{_escape_html(entry.title)}</span>
            </a>''')

    html_parts.append("</div>")
    return "".join(html_parts)


def _build_recent_list(
    entries: list[EntryData],
    current_path: str = "",
    base_url: str = "",
    limit: int = 20,
) -> str:
    """Build HTML for the recent entries list in sidebar.

    Args:
        entries: All entry data
        current_path: Currently viewed entry path (for highlighting)
        base_url: Base URL prefix for links
        limit: Maximum number of recent entries to show

    Returns:
        HTML string for the recent entries list
    """
    # Sort by most recent update (fall back to created)
    sorted_entries = sorted(entries, key=_recent_sort_key, reverse=True)[:limit]

    html_parts = ['<div class="tree" id="recent-list">']

    for entry in sorted_entries:
        active_class = " active" if entry.path == current_path else ""
        html_parts.append(f'''
            <a href="{base_url}/{entry.path}.html" class="tree-item{active_class}">
                <span class="tree-icon file">◇</span>
                <span class="tree-label">{_escape_html(entry.title)}</span>
            </a>''')

    html_parts.append("</div>")
    return "".join(html_parts)


def _build_tabbed_sidebar(
    entries: list[EntryData],
    current_path: str = "",
    base_url: str = "",
) -> str:
    """Build HTML for the tabbed sidebar with Browse and Recent tabs.

    Args:
        entries: All entry data
        current_path: Currently viewed entry path (for highlighting)
        base_url: Base URL prefix for links

    Returns:
        HTML string for the complete tabbed sidebar content
    """
    tree_html = _build_file_tree(entries, current_path, base_url)
    recent_html = _build_recent_list(entries, current_path, base_url)

    # Use "Categories" if KB has subfolders, "Entries" if flat structure
    has_folders = any("/" in e.path for e in entries)
    browse_heading = "Categories" if has_folders else "Entries"

    return f"""
            <div class="nav-tabs">
                <button class="nav-tab active" data-tab="tree">Browse</button>
                <button class="nav-tab" data-tab="recent">Recent</button>
            </div>

            <div class="sidebar-section" id="tree-section">
                <div class="sidebar-header">{browse_heading}</div>
                {tree_html}
            </div>

            <div class="sidebar-section" id="recent-section" style="display: none;">
                <div class="sidebar-header">Recent Updates</div>
                {recent_html}
            </div>"""


def _build_link_panel(
    outlinks: list[str],
    backlinks: list[str],
    relation_outgoing: list[RelationLink],
    relation_backlinks: list[RelationLink],
    entries_dict: dict[str, EntryData],
    base_url: str,
) -> str:
    """Build HTML for the right panel with links and typed relations.

    Args:
        outlinks: List of outgoing link paths
        backlinks: List of incoming link paths
        relation_outgoing: Typed relations originating from this entry
        relation_backlinks: Typed relations pointing to this entry
        entries_dict: Dict mapping path to EntryData for title lookup
        base_url: Base URL for links

    Returns:
        HTML string for the panel content
    """
    html_parts = []

    # Outgoing links section
    html_parts.append("""
        <div class="panel-section">
            <div class="panel-header">Outgoing Links</div>
            <ul class="link-list">""")

    if outlinks:
        for path in sorted(outlinks):
            title = entries_dict.get(path, None)
            title_text = title.title if title else path.split("/")[-1]
            html_parts.append(f'''
                <li class="link-item">
                    <a href="{base_url}/{path}.html">
                        <div class="link-title">{_escape_html(title_text)}</div>
                        <div class="link-path">{_escape_html(path)}</div>
                    </a>
                </li>''')
    else:
        html_parts.append('<li class="empty-state">No outgoing links</li>')

    html_parts.append("""
            </ul>
        </div>""")

    # Backlinks section
    html_parts.append("""
        <div class="panel-section">
            <div class="panel-header">Backlinks</div>
            <ul class="link-list">""")

    if backlinks:
        for path in sorted(backlinks):
            title = entries_dict.get(path, None)
            title_text = title.title if title else path.split("/")[-1]
            html_parts.append(f'''
                <li class="link-item">
                    <a href="{base_url}/{path}.html">
                        <div class="link-title">{_escape_html(title_text)}</div>
                        <div class="link-path">{_escape_html(path)}</div>
                    </a>
                </li>''')
    else:
        html_parts.append('<li class="empty-state">No backlinks</li>')

    html_parts.append("""
            </ul>
        </div>""")

    # Typed relations section
    html_parts.append("""
        <div class="panel-section">
            <div class="panel-header">Typed Relations</div>""")

    if relation_outgoing or relation_backlinks:
        if relation_outgoing:
            html_parts.append("""
            <div class="relation-group">
                <div class="relation-subheader">Outgoing</div>
                <ul class="link-list">""")

            for relation in sorted(relation_outgoing, key=lambda r: (r.path, r.type)):
                title = entries_dict.get(relation.path, None)
                title_text = title.title if title else relation.path.split("/")[-1]
                html_parts.append(f'''
                    <li class="link-item relation-item">
                        <a href="{base_url}/{relation.path}.html">
                            <div class="link-title">{_escape_html(title_text)}</div>
                            <div class="link-path">{_escape_html(relation.path)}</div>
                            <div class="relation-meta">
                                <span class="relation-type">{_escape_html(relation.type)}</span>
                                <span class="relation-direction" aria-hidden="true">→</span>
                            </div>
                        </a>
                    </li>''')

            html_parts.append("""
                </ul>
            </div>""")

        if relation_backlinks:
            html_parts.append("""
            <div class="relation-group">
                <div class="relation-subheader">Incoming</div>
                <ul class="link-list">""")

            for relation in sorted(relation_backlinks, key=lambda r: (r.path, r.type)):
                title = entries_dict.get(relation.path, None)
                title_text = title.title if title else relation.path.split("/")[-1]
                html_parts.append(f'''
                    <li class="link-item relation-item">
                        <a href="{base_url}/{relation.path}.html">
                            <div class="link-title">{_escape_html(title_text)}</div>
                            <div class="link-path">{_escape_html(relation.path)}</div>
                            <div class="relation-meta">
                                <span class="relation-direction" aria-hidden="true">←</span>
                                <span class="relation-type">{_escape_html(relation.type)}</span>
                            </div>
                        </a>
                    </li>''')

            html_parts.append("""
                </ul>
            </div>""")
    else:
        html_parts.append('<p class="empty-state">No typed relations</p>')

    html_parts.append("""
        </div>""")

    return "".join(html_parts)


def _base_layout(
    title: str,
    base_url: str,
    sidebar_html: str,
    main_html: str,
    panel_html: str,
    current_view: str = "reader",
    site_title: str = "Memex",
) -> str:
    """Generate complete HTML page with 3-column layout.

    Args:
        title: Page title
        base_url: Base URL for assets
        sidebar_html: HTML for left sidebar content
        main_html: HTML for main content area
        panel_html: HTML for right panel content
        current_view: Current view for nav highlighting ("reader" or "graph")
        site_title: Site title for header and <title> tag
    """
    reader_active = " active" if current_view == "reader" else ""
    graph_active = " active" if current_view == "graph" else ""

    return f'''<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover">
    <meta name="theme-color" content="#2e3440">
    <title>{_escape_html(title)} - {_escape_html(site_title)}</title>
    <link rel="preconnect" href="https://fonts.googleapis.com">
    <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
    <link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600&family=JetBrains+Mono:wght@300;400;500&display=swap"
          rel="stylesheet">
    <link rel="stylesheet" href="{base_url}/assets/style.css">
    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/github-dark.min.css">
    <script src="https://cdn.jsdelivr.net/npm/lunr@2.3.9/lunr.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/highlight.min.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/mermaid@10/dist/mermaid.min.js"></script>
    <script>window.BASE_URL = "{base_url}";</script>
</head>
<body>
    <div class="app">
        <!-- Header -->
        <header class="header">
            <!-- Mobile menu button (hamburger) -->
            <button class="mobile-menu-btn" aria-label="Open navigation menu">
                <span></span>
                <span></span>
                <span></span>
            </button>

            <a href="{base_url}/" class="logo">
                <div class="logo-mark"></div>
                <div class="logo-text">{_escape_html(site_title)}<span> / knowledge</span></div>
            </a>

            <div class="search-container">
                <svg class="search-icon" width="16" height="16" viewBox="0 0 24 24"
                     fill="none" stroke="currentColor" stroke-width="2">
                    <circle cx="11" cy="11" r="8"></circle>
                    <path d="m21 21-4.35-4.35"></path>
                </svg>
                <input
                    type="text"
                    id="search-input"
                    placeholder="Search knowledge base..."
                    autocomplete="off"
                >
                <div id="search-results"></div>
            </div>

            <div class="header-nav">
                <a href="{base_url}/" class="nav-link{reader_active}">Reader</a>
                <a href="{base_url}/graph.html" class="nav-link{graph_active}">Graph</a>
            </div>

            <!-- Mobile search button -->
            <button class="mobile-search-btn" aria-label="Open search">
                <svg width="20" height="20" viewBox="0 0 24 24"
                     fill="none" stroke="currentColor" stroke-width="2">
                    <circle cx="11" cy="11" r="8"></circle>
                    <path d="m21 21-4.35-4.35"></path>
                </svg>
            </button>
        </header>

        <!-- Sidebar overlay (mobile) -->
        <div class="sidebar-overlay"></div>

        <!-- Sidebar -->
        <aside class="sidebar">
            {sidebar_html}
            <div class="sidebar-footer">
                <a href="https://github.com/chriskd/memex"
                   target="_blank"
                   rel="noopener">Powered by memex</a>
            </div>
        </aside>

        <!-- Main content -->
        <main class="main">
            {main_html}
        </main>

        <!-- Right panel -->
        <aside class="panel">
            {panel_html}
        </aside>
    </div>

    <!-- Mobile search overlay -->
    <div class="search-overlay">
        <div class="search-overlay-header">
            <button class="search-overlay-close" aria-label="Close search">
                <svg width="24" height="24" viewBox="0 0 24 24"
                     fill="none" stroke="currentColor" stroke-width="2">
                    <path d="M18 6L6 18"></path>
                    <path d="M6 6l12 12"></path>
                </svg>
            </button>
            <input
                type="text"
                class="search-overlay-input"
                placeholder="Search..."
                autocomplete="off"
            >
        </div>
        <div class="search-overlay-results"></div>
    </div>

    <!-- Bottom navigation (mobile) -->
    <nav class="bottom-nav">
        <div class="bottom-nav-inner">
            <a href="{base_url}/" class="bottom-nav-link{reader_active}">
                <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
                    <path d="M4 19.5A2.5 2.5 0 0 1 6.5 17H20"></path>
                    <path d="M6.5 2H20v20H6.5A2.5 2.5 0 0 1 4 19.5v-15A2.5 2.5 0 0 1 6.5 2z"></path>
                </svg>
                <span>Reader</span>
            </a>
            <button class="bottom-nav-link bottom-nav-search">
                <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
                    <circle cx="11" cy="11" r="8"></circle>
                    <path d="m21 21-4.35-4.35"></path>
                </svg>
                <span>Search</span>
            </button>
            <a href="{base_url}/graph.html" class="bottom-nav-link{graph_active}">
                <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
                    <circle cx="12" cy="12" r="2"></circle>
                    <circle cx="6" cy="6" r="2"></circle>
                    <circle cx="18" cy="6" r="2"></circle>
                    <circle cx="6" cy="18" r="2"></circle>
                    <circle cx="18" cy="18" r="2"></circle>
                    <path d="M12 10V6M12 14v4M10 12H6M14 12h4"></path>
                </svg>
                <span>Graph</span>
            </a>
        </div>
    </nav>

    <script src="{base_url}/assets/search.js"></script>
    <script src="{base_url}/assets/sidebar.js"></script>
    <script>hljs.highlightAll(); mermaid.initialize({{startOnLoad: true, theme: 'dark'}});</script>
</body>
</html>
'''


# Entry page template content (inside reader-container)
ENTRY_TEMPLATE = """
<div class="reader-container">
    <article class="entry">
        <header class="entry-header">
            <a href="{{ base_url }}/{{ entry.path }}.html" class="entry-path">{{ entry.path }}</a>
            <div class="entry-meta">
                {% if entry.metadata.created %}
                <div class="entry-meta-item">
                    Created: <span>{{ entry.metadata.created|datefmt }}</span>
                </div>
                {% endif %}
                {% if entry.metadata.updated %}
                <div class="entry-meta-item">
                    Updated: <span>{{ entry.metadata.updated|datefmt }}</span>
                </div>
                {% endif %}
            </div>
            {% if entry.tags %}
            <div class="entry-tags">
                {% for tag in entry.tags %}
                <a href="{{ base_url }}/tags/{{ tag }}.html" class="tag">{{ tag }}</a>
                {% endfor %}
            </div>
            {% endif %}
        </header>
        <div class="entry-content">
            {{ html_content }}
        </div>
    </article>
</div>
"""

# Index page template content
INDEX_TEMPLATE = """
<div class="index-container">
    <h1>Knowledge Base</h1>
    <section class="index-section">
        <h2>Recent Entries</h2>
        <ul class="entry-list">
            {% for entry in recent_entries %}
            <li>
                <a href="{{ base_url }}/{{ entry.path }}.html">{{ entry.title }}</a>
                {% if entry.metadata.created %}
                <span class="entry-date">{{ entry.metadata.created|datefmt }}</span>
                {% endif %}
            </li>
            {% endfor %}
        </ul>
    </section>
    <section class="index-section">
        <h2>Tags</h2>
        <div class="tags-cloud">
            {% for tag, count in tags_with_counts %}
            <a href="{{ base_url }}/tags/{{ tag }}.html" class="tag">
                {{ tag }} ({{ count }})
            </a>
            {% endfor %}
        </div>
    </section>
</div>
"""

# Tag page template content
TAG_TEMPLATE = """
<div class="tag-page-container">
    <h1>Tag: {{ tag }}</h1>
    <p class="tag-count">{{ entries | length }} entries</p>
    <ul class="entry-list">
        {% for entry in entries %}
        <li>
            <a href="{{ base_url }}/{{ entry.path }}.html">{{ entry.title }}</a>
            {% if entry.metadata.created %}
            <span class="entry-date">{{ entry.metadata.created|datefmt }}</span>
            {% endif %}
        </li>
        {% endfor %}
    </ul>
    <p class="back-link"><a href="{{ base_url }}/">← Back to index</a></p>
</div>
"""


def _get_env() -> Environment:
    """Create Jinja2 environment with autoescape enabled and custom filters."""
    env = Environment(
        loader=BaseLoader(),
        autoescape=select_autoescape(default=True, default_for_string=True),
    )
    env.filters["datefmt"] = _format_date
    return env


def render_entry_page(
    entry: EntryData,
    base_url: str,
    site_title: str = "Memex",
    all_entries: list[EntryData] | None = None,
    entries_dict: dict[str, EntryData] | None = None,
) -> str:
    """Render a single entry page with full 3-column layout.

    Args:
        entry: Entry data including content and metadata
        base_url: Base URL for links
        site_title: Site title for header and <title> tag
        all_entries: All entries for sidebar navigation
        entries_dict: Dict mapping path to EntryData for title lookup

    Returns:
        Complete HTML page string
    """
    env = _get_env()
    all_entries = all_entries or []
    entries_dict = entries_dict or {}

    # Build tabbed sidebar
    sidebar_html = _build_tabbed_sidebar(all_entries, entry.path, base_url)

    # Build main content
    tmpl = env.from_string(ENTRY_TEMPLATE)
    main_html = tmpl.render(
        entry=entry,
        base_url=base_url,
        html_content=_safe(entry.html_content),
    )

    # Build right panel
    panel_html = _build_link_panel(
        entry.outlinks,
        entry.backlinks,
        entry.relation_outgoing,
        entry.relation_backlinks,
        entries_dict,
        base_url,
    )

    return _base_layout(
        title=entry.title,
        base_url=base_url,
        sidebar_html=sidebar_html,
        main_html=main_html,
        panel_html=panel_html,
        current_view="reader",
        site_title=site_title,
    )


def render_index_page(
    entries: list[EntryData],
    tags_index: dict[str, list[str]],
    base_url: str,
    site_title: str = "Memex",
) -> str:
    """Render the main index page with full 3-column layout.

    Args:
        entries: All entry data
        tags_index: Dict mapping tag -> list of entry paths
        base_url: Base URL for links
        site_title: Site title for header and <title> tag

    Returns:
        Complete HTML page string
    """
    env = _get_env()

    # Build tabbed sidebar
    sidebar_html = _build_tabbed_sidebar(entries, base_url=base_url)

    # Sort entries by most recent update (fall back to created)
    recent_entries = sorted(entries, key=_recent_sort_key, reverse=True)[:20]

    # Build tags with counts
    tags_with_counts = sorted(
        [(tag, len(paths)) for tag, paths in tags_index.items()], key=lambda x: (-x[1], x[0])
    )

    tmpl = env.from_string(INDEX_TEMPLATE)
    main_html = tmpl.render(
        base_url=base_url,
        recent_entries=recent_entries,
        tags_with_counts=tags_with_counts,
    )

    # Empty panel for index page
    panel_html = """
        <div class="panel-section">
            <div class="panel-header">Welcome</div>
            <p class="empty-state">Select an entry to view its connections</p>
        </div>
    """

    return _base_layout(
        title="Home",
        base_url=base_url,
        sidebar_html=sidebar_html,
        main_html=main_html,
        panel_html=panel_html,
        current_view="reader",
        site_title=site_title,
    )


def render_tag_page(
    tag: str,
    entries: list[EntryData],
    base_url: str,
    site_title: str = "Memex",
    all_entries: list[EntryData] | None = None,
) -> str:
    """Render a tag listing page with full 3-column layout.

    Args:
        tag: The tag name
        entries: Entries with this tag
        base_url: Base URL for links
        site_title: Site title for header and <title> tag
        all_entries: All entries for sidebar navigation

    Returns:
        Complete HTML page string
    """
    env = _get_env()
    all_entries = all_entries or entries

    # Build tabbed sidebar
    sidebar_html = _build_tabbed_sidebar(all_entries, base_url=base_url)

    # Sort entries alphabetically by title
    sorted_entries = sorted(entries, key=lambda e: e.title.lower())

    tmpl = env.from_string(TAG_TEMPLATE)
    main_html = tmpl.render(
        tag=tag,
        base_url=base_url,
        entries=sorted_entries,
    )

    # Empty panel for tag page
    panel_html = """
        <div class="panel-section">
            <div class="panel-header">Tag Info</div>
            <p class="empty-state">Entries tagged with this topic</p>
        </div>
    """

    return _base_layout(
        title=f"Tag: {tag}",
        base_url=base_url,
        sidebar_html=sidebar_html,
        main_html=main_html,
        panel_html=panel_html,
        current_view="reader",
        site_title=site_title,
    )


def render_graph_page(
    base_url: str,
    site_title: str = "Memex",
    all_entries: list[EntryData] | None = None,
) -> str:
    """Render the graph visualization page with full 3-column layout.

    Args:
        base_url: Base URL for links
        site_title: Site title for header and <title> tag
        all_entries: All entries for sidebar navigation

    Returns:
        Complete HTML page string with D3.js graph visualization
    """
    all_entries = all_entries or []

    # Build tabbed sidebar
    sidebar_html = _build_tabbed_sidebar(all_entries, base_url=base_url)

    # Full-page graph with D3.js force simulation
    main_html = """
<div class="graph-container">
    <div id="graph"></div>
    <div id="graph-tooltip" class="graph-tooltip"></div>
    <div id="graph-controls" class="graph-controls">
        <div class="graph-controls-header">Relations</div>
        <div class="graph-control-group">
            <label class="graph-control-item">
                <input type="checkbox" data-origin="wikilink" checked>
                <span>Wikilinks</span>
            </label>
            <label class="graph-control-item">
                <input type="checkbox" data-origin="semantic" checked>
                <span>Semantic</span>
            </label>
            <label class="graph-control-item">
                <input type="checkbox" data-origin="relations" checked>
                <span>Typed</span>
            </label>
        </div>
        <div class="graph-control-group">
            <div class="graph-control-subheader">Types</div>
            <div id="graph-relation-types" class="graph-control-list"></div>
        </div>
    </div>
</div>
<script src="https://cdn.jsdelivr.net/npm/d3@7/dist/d3.min.js"></script>
<script>
(function() {
    const baseUrl = window.BASE_URL || '';

    fetch(baseUrl + '/graph.json')
        .then(r => r.json())
        .then(data => {
            const container = document.getElementById('graph');
            const tooltip = document.getElementById('graph-tooltip');
            const controls = document.getElementById('graph-controls');
            const relationTypesContainer = document.getElementById('graph-relation-types');
            const width = container.clientWidth || 960;
            const height = container.clientHeight || 600;

            const svg = d3.select('#graph')
                .append('svg')
                .attr('width', '100%')
                .attr('height', '100%')
                .attr('viewBox', [0, 0, width, height]);

            // Zoom behavior
            const g = svg.append('g');
            svg.call(d3.zoom()
                .extent([[0, 0], [width, height]])
                .scaleExtent([0.1, 4])
                .on('zoom', (event) => g.attr('transform', event.transform)));

            // Force simulation
            const simulation = d3.forceSimulation(data.nodes)
                .force('link', d3.forceLink(data.edges).id(d => d.id).distance(80))
                .force('charge', d3.forceManyBody().strength(-200))
                .force('center', d3.forceCenter(width / 2, height / 2))
                .force('collision', d3.forceCollide().radius(20));

            const edgeColors = {
                'wikilink': '#262b3a',
                'semantic': '#8fbcbb',
                'relations': '#5e81ac'
            };

            const nodeRadius = 8;
            const arrowOffset = nodeRadius + 6;

            const defs = svg.append('defs');
            defs.append('marker')
                .attr('id', 'arrow-relations')
                .attr('viewBox', '0 -5 10 10')
                .attr('refX', 10)
                .attr('refY', 0)
                .attr('markerWidth', 6)
                .attr('markerHeight', 6)
                .attr('orient', 'auto')
                .append('path')
                .attr('d', 'M0,-5L10,0L0,5')
                .attr('fill', edgeColors['relations']);

            // Links
            const link = g.append('g')
                .attr('class', 'links')
                .selectAll('line')
                .data(data.edges)
                .join('line')
                .attr('stroke', d => edgeColors[d.origin] || '#262b3a')
                .attr('stroke-opacity', 0.5)
                .attr('stroke-width', d => d.score ? 1 + (d.score * 2) : 1)
                .attr('stroke-dasharray', d => {
                    if (d.origin === 'wikilink') return '2,2';
                    if (d.origin === 'semantic') return '1,4';
                    return null;
                })
                .attr('marker-end', d => d.origin === 'relations' ? 'url(#arrow-relations)' : null)
                .attr('data-origin', d => d.origin)
                .attr('data-type', d => d.type || '');

            // Nodes
            const node = g.append('g')
                .attr('class', 'nodes')
                .selectAll('g')
                .data(data.nodes)
                .join('g')
                .call(d3.drag()
                    .on('start', (event, d) => {
                        if (!event.active) simulation.alphaTarget(0.3).restart();
                        d.fx = d.x;
                        d.fy = d.y;
                    })
                    .on('drag', (event, d) => {
                        d.fx = event.x;
                        d.fy = event.y;
                    })
                    .on('end', (event, d) => {
                        if (!event.active) simulation.alphaTarget(0);
                        d.fx = null;
                        d.fy = null;
                    }));

            // Category colors - Nord Aurora palette
            const categoryColors = {
                'devops': '#d08770',        // nord12 - orange
                'infrastructure': '#88c0d0', // nord8 - cyan
                'projects': '#a3be8c',       // nord14 - green
                'tooling': '#bf616a',        // nord11 - red
                'development': '#b48ead',    // nord15 - purple
                'best-practices': '#ebcb8b', // nord13 - yellow
                'default': '#81a1c1'         // nord9 - blue
            };

            function getNodeColor(d) {
                const path = d.id || '';
                const category = path.split('/')[0];
                return categoryColors[category] || categoryColors['default'];
            }

            node.append('circle')
                .attr('r', 8)
                .attr('fill', d => getNodeColor(d))
                .attr('stroke', d => getNodeColor(d))
                .attr('stroke-opacity', 0.3)
                .attr('stroke-width', 8)
                .style('filter', 'drop-shadow(0 0 4px currentColor)');

            node.append('text')
                .text(d => d.title)
                .attr('x', 14)
                .attr('y', 4)
                .attr('font-family', "'JetBrains Mono', monospace")
                .attr('font-size', '10px')
                .attr('fill', '#9ca3af')
                .attr('opacity', 0.8);

            const relationTypes = Array.from(new Set(
                data.edges
                    .filter(edge => edge.origin === 'relations' && edge.type)
                    .map(edge => edge.type)
            )).sort();

            const hasTypeFilters = relationTypes.length > 0;
            const activeTypes = new Set(relationTypes);
            const originEnabled = {
                wikilink: true,
                semantic: true,
                relations: true,
            };

            function isEdgeVisible(edge) {
                if (!originEnabled[edge.origin]) {
                    return false;
                }
                if (edge.origin === 'relations' && hasTypeFilters) {
                    if (!edge.type) {
                        return false;
                    }
                    return activeTypes.has(edge.type);
                }
                return true;
            }

            function updateLinkVisibility() {
                link.style('display', d => isEdgeVisible(d) ? null : 'none');
            }

            function edgeTargetPosition(edge, offset) {
                const dx = edge.target.x - edge.source.x;
                const dy = edge.target.y - edge.source.y;
                const length = Math.sqrt(dx * dx + dy * dy) || 1;
                return {
                    x: edge.target.x - (dx / length) * offset,
                    y: edge.target.y - (dy / length) * offset,
                };
            }

            if (relationTypesContainer) {
                relationTypesContainer.textContent = '';
                if (!relationTypes.length) {
                    const empty = document.createElement('div');
                    empty.className = 'graph-control-empty';
                    empty.textContent = 'No typed relations';
                    relationTypesContainer.appendChild(empty);
                } else {
                    relationTypes.forEach(type => {
                        const label = document.createElement('label');
                        label.className = 'graph-control-item';
                        const input = document.createElement('input');
                        input.type = 'checkbox';
                        input.checked = true;
                        input.dataset.type = type;
                        const span = document.createElement('span');
                        span.textContent = type;
                        label.appendChild(input);
                        label.appendChild(span);
                        relationTypesContainer.appendChild(label);
                    });
                }
            }

            if (controls) {
                const originInputs = controls.querySelectorAll('input[data-origin]');
                originInputs.forEach(input => {
                    input.addEventListener('change', event => {
                        const target = event.target;
                        if (!target || !target.dataset) {
                            return;
                        }
                        originEnabled[target.dataset.origin] = target.checked;
                        updateLinkVisibility();
                    });
                });
            }

            if (relationTypesContainer) {
                relationTypesContainer.addEventListener('change', event => {
                    const target = event.target;
                    if (!target || !target.dataset || !target.dataset.type) {
                        return;
                    }
                    if (target.checked) {
                        activeTypes.add(target.dataset.type);
                    } else {
                        activeTypes.delete(target.dataset.type);
                    }
                    updateLinkVisibility();
                });
            }

            updateLinkVisibility();

            // Hover effects
            node.on('mouseover', (event, d) => {
                    tooltip.style.display = 'block';
                    tooltip.innerHTML = '<strong>' + d.title + '</strong>' +
                        (d.tags.length ? '<br>Tags: ' + d.tags.join(', ') : '');
                    tooltip.style.left = (event.pageX + 10) + 'px';
                    tooltip.style.top = (event.pageY + 10) + 'px';
                })
                .on('mouseout', () => {
                    tooltip.style.display = 'none';
                })
                .on('click', (event, d) => {
                    window.location.href = baseUrl + '/' + d.url;
                });

            link.on('mouseover', (event, d) => {
                    tooltip.style.display = 'block';
                    const label = d.origin === 'relations'
                        ? (d.type || 'relation')
                        : (d.origin === 'semantic' ? 'semantic' : 'wikilink');
                    const sourceLabel = d.source.title || d.source.id;
                    const targetLabel = d.target.title || d.target.id;
                    const score = d.score ? ' (' + d.score.toFixed(2) + ')' : '';
                    tooltip.innerHTML = '<strong>' + label + '</strong>' +
                        '<br>' + sourceLabel + ' → ' + targetLabel + score;
                    tooltip.style.left = (event.pageX + 10) + 'px';
                    tooltip.style.top = (event.pageY + 10) + 'px';
                })
                .on('mouseout', () => {
                    tooltip.style.display = 'none';
                });

            // Simulation tick
            simulation.on('tick', () => {
                link.attr('x1', d => d.source.x)
                    .attr('y1', d => d.source.y)
                    .attr('x2', d => {
                        const offset = d.origin === 'relations' ? arrowOffset : nodeRadius;
                        return edgeTargetPosition(d, offset).x;
                    })
                    .attr('y2', d => {
                        const offset = d.origin === 'relations' ? arrowOffset : nodeRadius;
                        return edgeTargetPosition(d, offset).y;
                    });
                node.attr('transform', d => `translate(${d.x},${d.y})`);
            });
        });
})();
</script>
"""

    # Empty panel for graph page
    panel_html = """
        <div class="panel-section">
            <div class="panel-header">Graph View</div>
            <p class="empty-state">Click a node to navigate to that entry</p>
        </div>
    """

    return _base_layout(
        title="Graph",
        base_url=base_url,
        sidebar_html=sidebar_html,
        main_html=main_html,
        panel_html=panel_html,
        current_view="graph",
        site_title=site_title,
    )
