---
// src/components/MiniKnowledgeGraph.astro
// Enhanced smaller version of the Knowledge Graph for blog posts with Obsidian-like physics

export interface GraphNode {
  id: string;
  label: string;
  type: 'post' | 'tag' | 'category' | 'current';
  category?: string;
  tags?: string[];
  url?: string;
}

export interface GraphEdge {
  source: string;
  target: string;
  type: 'post-tag' | 'post-category' | 'post-post';
  strength?: number;
}

export interface GraphData {
  nodes: GraphNode[];
  edges: GraphEdge[];
}

interface Props {
  currentPost: any;  // Current post data
  relatedPosts?: any[]; // Optional related posts array
  height?: string;
}

const { currentPost, relatedPosts = [], height = "250px" } = Astro.props;

// Prepare tags as nodes
const tagNodes = (currentPost.data.tags || []).map(tag => ({
  id: `tag-${tag}`,
  label: tag,
  type: 'tag',
  url: `/tag/${tag}/`
}));

// Create edges from the current post to its tags
const currentPostTagEdges = (currentPost.data.tags || []).map(tag => ({
  source: currentPost.slug,
  target: `tag-${tag}`,
  type: 'post-tag',
  strength: 2 // Stronger connection for current post
}));

// Create the current post node with a special type
const currentPostNode = {
  id: currentPost.slug,
  label: currentPost.data.title,
  type: 'current', // Special type for styling
  category: currentPost.data.category || 'Uncategorized',
  tags: currentPost.data.tags || [],
  url: `/posts/${currentPost.slug}/`
};

// Process related posts
const relatedPostNodes = relatedPosts.map(post => ({
  id: post.slug,
  label: post.data.title,
  type: 'post',
  category: post.data.category || 'Uncategorized',
  tags: post.data.tags || [],
  url: `/posts/${post.slug}/`
}));

// Create edges from related posts to their tags that are also current post tags
// This ensures we only show connections relevant to the current post
const relatedPostTagEdges = [];
relatedPosts.forEach(post => {
  // For each tag that is also in current post, create an edge
  (post.data.tags || []).filter(tag => 
    (currentPost.data.tags || []).includes(tag)
  ).forEach(tag => {
    relatedPostTagEdges.push({
      source: post.slug,
      target: `tag-${tag}`,
      type: 'post-tag',
      strength: 1
    });
  });
  
  // Also create edges between related posts and current post
  relatedPostTagEdges.push({
    source: currentPost.slug,
    target: post.slug,
    type: 'post-post',
    strength: 1
  });
});

// Combine all nodes and edges
const graphData = {
  nodes: [currentPostNode, ...tagNodes, ...relatedPostNodes],
  edges: [...currentPostTagEdges, ...relatedPostTagEdges]
};

// Define node colors, radii and edge colors
const nodeColors = {
  currentArticle: "#FF5733",
  relatedArticle: "#3366CC",
  tag: "#33CC66",
  category: "#9966CC" // Add category color
};

const nodeRadii = {
  currentArticle: 20,
  relatedArticle: 15,
  tag: 12,
  category: 18 // Add category size
};

// Calculate node sizes
const nodeSizes = {};
// Current post should be largest
nodeSizes[currentPost.slug] = 25;
// Tag sizes are middle
tagNodes.forEach(node => {
  nodeSizes[node.id] = 18;
});
// Related posts are smaller
relatedPostNodes.forEach(node => {
  nodeSizes[node.id] = 20;
});

// Default physics settings with Obsidian-like behavior
const defaultPhysics = {
  nodeRepulsion: 7500,      // Increased to prevent nodes from getting too close
  edgeElasticity: 0.35,     // More flexible edges like Obsidian
  gravity: 0.4,             // Light gravity to keep nodes centered
  animate: true,
  damping: 0.12,            // Mimics Obsidian's smooth drag effect
  pullStrength: 0.09        // Strength of pull effect when dragging nodes
};
---

<!-- Include Cytoscape via CDN if not already loaded in parent -->
<script src="https://unpkg.com/cytoscape@3.25.0/dist/cytoscape.min.js" is:inline></script>
<script src="https://unpkg.com/cytoscape-cose-bilkent@4.1.0/cytoscape-cose-bilkent.js" is:inline></script>

<div id="mini-graph-area" class="mini-graph-area">
  <h3 class="mini-graph-title">{title || 'Article Connections'}</h3>
  
  <!-- How to use info button -->
  <div class="info-button-container">
    <button id="graph-info-button" class="info-button" aria-label="How to use the Knowledge Graph">
      <svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
        <circle cx="12" cy="12" r="10"></circle>
        <line x1="12" y1="16" x2="12" y2="12"></line>
        <line x1="12" y1="8" x2="12.01" y2="8"></line>
      </svg>
      <span>How to use the Knowledge Graph</span>
    </button>
    <div id="graph-info-panel" class="info-panel">
      <h4>How to use the Knowledge Graph</h4>
      <ul>
        <li>Click on nodes to see connections and navigate</li>
        <li>Drag nodes to rearrange the graph</li>
        <li>Use the zoom controls to zoom in/out</li>
        <li>Click the fullscreen button for a larger view</li>
        <li>Adjust physics settings with the physics controls</li>
      </ul>
    </div>
  </div>

  <!-- Cytoscape Container -->
  <div id="mini-cy" class="mini-cy"></div>
  
  <!-- Physics Controls Panel -->
  <div class="physics-controls">
    <div class="physics-header">
      <button id="toggle-physics-panel" class="physics-toggle-btn" aria-label="Toggle physics controls">
        <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"></circle><path d="M9.09 9a3 3 0 0 1 5.83 1c0 2-3 3-3 3"></path><line x1="12" y1="17" x2="12.01" y2="17"></line></svg>
      </button>
    </div>
    <div id="physics-panel" class="physics-panel">
      <div class="physics-control-group">
        <label class="physics-label">Repulsion: <span id="repulsion-value">7500</span></label>
        <input type="range" id="node-repulsion" class="physics-slider" min="1000" max="15000" step="500" value="7500">
      </div>
      <div class="physics-control-group">
        <label class="physics-label">Elasticity: <span id="elasticity-value">0.35</span></label>
        <input type="range" id="edge-elasticity" class="physics-slider" min="0.05" max="1" step="0.05" value="0.35">
      </div>
      <div class="physics-control-group">
        <label class="physics-label">Gravity: <span id="gravity-value">0.4</span></label>
        <input type="range" id="gravity" class="physics-slider" min="0" max="1" step="0.05" value="0.4">
      </div>
      <div class="physics-actions">
        <button id="reset-physics" class="physics-btn">Reset</button>
        <button id="apply-physics" class="physics-btn primary">Apply</button>
      </div>
    </div>
  </div>
  
  <!-- Graph Controls -->
  <div class="graph-controls">
    <button id="reset-view" class="graph-control-btn" aria-label="Reset view">
      <svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M3 2v6h6"></path><path d="M21 12A9 9 0 0 0 6 5.3L3 8"></path><path d="M21 22v-6h-6"></path><path d="M3 12a9 9 0 0 0 15 6.7l3-2.7"></path></svg>
    </button>
    <button id="zoom-in" class="graph-control-btn" aria-label="Zoom in">
      <svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="11" cy="11" r="8"></circle><line x1="21" y1="21" x2="16.65" y2="16.65"></line><line x1="11" y1="8" x2="11" y2="14"></line><line x1="8" y1="11" x2="14" y2="11"></line></svg>
    </button>
    <button id="zoom-out" class="graph-control-btn" aria-label="Zoom out">
      <svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="11" cy="11" r="8"></circle><line x1="21" y1="21" x2="16.65" y2="16.65"></line><line x1="8" y1="11" x2="14" y2="11"></line></svg>
    </button>
    <button id="fullscreen" class="graph-control-btn" aria-label="Fullscreen">
      <svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M8 3H5a2 2 0 0 0-2 2v3"></path><path d="M21 8V5a2 2 0 0 0-2-2h-3"></path><path d="M3 16v3a2 2 0 0 0 2 2h3"></path><path d="M16 21h3a2 2 0 0 0 2-2v-3"></path></svg>
    </button>
  </div>
  
  <!-- For debugging purposes -->
  <div id="mini-graph-error" class="mini-graph-error" style="display: none;"></div>
  
  <!-- Simple Legend -->
  <div class="mini-graph-legend">
    <div class="legend-item">
      <span class="legend-color" style={`background-color: ${nodeColors.currentArticle};`}></span>
      <span class="legend-label">Current Article</span>
    </div>
    <div class="legend-item">
      <span class="legend-color" style={`background-color: ${nodeColors.relatedArticle};`}></span>
      <span class="legend-label">Related Articles</span>
    </div>
    <div class="legend-item">
      <span class="legend-color" style={`background-color: ${nodeColors.category};`}></span>
      <span class="legend-label">Category</span>
    </div>
    <div class="legend-item">
      <span class="legend-color" style={`background-color: ${nodeColors.tag};`}></span>
      <span class="legend-label">Tags</span>
    </div>
  </div>

  <!-- Loading indicator -->
  <div id="mini-graph-loading" class="mini-graph-loading">
    <div class="loading-spinner"></div>
    <div class="loading-text">Loading graph...</div>
  </div>
  
  <!-- Fullscreen Mode Container -->
  <div id="fullscreen-container" class="fullscreen-container">
    <div class="fullscreen-header">
      <h2 class="fullscreen-title">Knowledge Graph Explorer</h2>
      <div class="fullscreen-controls">
        <button id="exit-fullscreen" class="fullscreen-btn">
          <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M8 3v3a2 2 0 0 1-2 2H3"></path><path d="M21 8h-3a2 2 0 0 1-2-2V3"></path><path d="M3 16h3a2 2 0 0 1 2 2v3"></path><path d="M16 21v-3a2 2 0 0 1 2-2h3"></path></svg>
          Exit Fullscreen
        </button>
      </div>
    </div>
    <div class="fullscreen-content">
      <div id="fullscreen-graph-container" class="fullscreen-graph"></div>
      <div class="fullscreen-article">
        <div id="article-placeholder" class="article-placeholder">
          <svg xmlns="http://www.w3.org/2000/svg" width="64" height="64" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1" stroke-linecap="round" stroke-linejoin="round"><path d="M2 3h6a4 4 0 0 1 4 4v14a3 3 0 0 0-3-3H2z"></path><path d="M22 3h-6a4 4 0 0 0-4 4v14a3 3 0 0 1 3-3h7z"></path></svg>
          <p>Select a post in the graph to view its content</p>
        </div>
        <div id="article-content" class="article-content"></div>
      </div>
    </div>
  </div>
</div>

<style is:global>
  .mini-graph-wrapper {
    width: 100%;
    height: var(--graph-height, 250px);
    min-height: 200px;
    margin: 1rem 0 2rem;
    position: relative;
    border-radius: 10px;
    border: 1px solid var(--card-border);
    background: rgba(15, 23, 42, 0.15);
    backdrop-filter: blur(5px);
    overflow: hidden;
    display: block !important; /* Force display */
  }
  
  .mini-graph-title {
    position: absolute;
    top: 10px;
    left: 15px;
    margin: 0;
    font-size: 1rem;
    z-index: 5;
    color: var(--text-primary);
    font-weight: 500;
  }
  
  .mini-graph-container {
    width: 100%;
    height: 100%;
    position: relative;
    z-index: 4; /* Ensure graph nodes appear above other elements */
  }
  
  /* Info button styles */
  .info-button-container {
    position: absolute;
    top: 10px;
    left: 50%;
    transform: translateX(-50%);
    z-index: 20;
  }
  
  .info-button {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    background: rgba(15, 23, 42, 0.7);
    border: 1px solid var(--border-primary, #334155);
    border-radius: 20px;
    padding: 0.25rem 0.75rem;
    color: var(--text-primary, #e2e8f0);
    font-size: 0.8rem;
    cursor: pointer;
    transition: all 0.2s ease;
  }
  
  .info-button:hover {
    background: rgba(30, 41, 59, 0.8);
    border-color: var(--accent-primary, #3b82f6);
  }
  
  .info-panel {
    position: absolute;
    top: 100%;
    left: 50%;
    transform: translateX(-50%);
    margin-top: 0.5rem;
    width: 250px;
    background: rgba(15, 23, 42, 0.95);
    border: 1px solid var(--border-primary, #334155);
    border-radius: 8px;
    padding: 1rem;
    display: none;
    z-index: 25;
    box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
  }
  
  .info-panel.active {
    display: block;
  }
  
  .info-panel h4 {
    margin-top: 0;
    margin-bottom: 0.75rem;
    color: var(--text-primary, #e2e8f0);
    font-size: 0.95rem;
  }
  
  .info-panel ul {
    margin: 0;
    padding-left: 1.25rem;
  }
  
  .info-panel li {
    margin-bottom: 0.5rem;
    color: var(--text-secondary, #94a3b8);
    font-size: 0.85rem;
  }
  
  /* Physics Controls Panel - Fixed z-index and positioning */
  .physics-controls {
    position: absolute;
    top: 10px;
    right: 15px;
    z-index: 20; /* Higher z-index to appear above graph */
  }
  
  .physics-toggle-btn {
    background: rgba(15, 23, 42, 0.7);
    border: 1px solid var(--border-primary, #334155);
    border-radius: 50%;
    width: 28px;
    height: 28px;
    display: flex;
    align-items: center;
    justify-content: center;
    color: var(--text-primary, #e2e8f0);
    cursor: pointer;
    transition: all 0.2s ease;
  }
  
  .physics-toggle-btn:hover {
    background: rgba(15, 23, 42, 0.9);
    transform: rotate(15deg);
  }
  
  .physics-header {
    display: flex;
    justify-content: flex-end;
  }
  
  .physics-panel {
    display: none;
    background: rgba(15, 23, 42, 0.9);
    border: 1px solid var(--border-primary, #334155);
    border-radius: 8px;
    padding: 1rem;
    width: 220px;
    position: absolute;
    right: 0;
    top: 35px;
    z-index: 25;
    backdrop-filter: blur(10px);
  }
  
  .physics-panel.active {
    display: block;
  }
  
  .physics-control-group {
    margin-bottom: 1rem;
  }
  
  .physics-label {
    display: block;
    font-size: 0.8rem;
    color: var(--text-secondary);
    margin-bottom: 0.5rem;
  }
  
  .physics-slider {
    width: 100%;
    height: 4px;
    background: rgba(226, 232, 240, 0.1);
    border-radius: 2px;
    outline: none;
    appearance: none;
  }
  
  .physics-slider::-webkit-slider-thumb {
    appearance: none;
    width: 14px;
    height: 14px;
    border-radius: 50%;
    background: var(--accent-primary);
    cursor: pointer;
    border: none;
  }
  
  .physics-slider::-moz-range-thumb {
    width: 14px;
    height: 14px;
    border-radius: 50%;
    background: var(--accent-primary);
    cursor: pointer;
    border: none;
  }
  
  .physics-btn {
    background: rgba(226, 232, 240, 0.05);
    color: var(--text-primary);
    border: 1px solid var(--border-primary);
    padding: 0.5rem 0.75rem;
    border-radius: 4px;
    cursor: pointer;
    font-size: 0.85rem;
    margin-right: 0.5rem;
    transition: all 0.2s ease;
  }
  
  .physics-btn:hover {
    background: rgba(226, 232, 240, 0.1);
    border-color: var(--accent-primary);
  }
  
  .physics-btn.primary {
    background: var(--accent-primary, #38bdf8);
    color: var(--bg-primary, #0f172a);
    border-color: var(--accent-primary, #38bdf8);
  }
  
  .physics-actions {
    display: flex;
    justify-content: flex-end;
  }
  
  /* Graph Controls - Fixed z-index and position */
  .graph-controls {
    position: absolute;
    bottom: 10px;
    right: 15px;
    display: flex;
    gap: 8px;
    z-index: 10;
  }
  
  .graph-control-btn {
    background: rgba(15, 23, 42, 0.7);
    border: 1px solid var(--border-primary);
    border-radius: 50%;
    width: 28px;
    height: 28px;
    display: flex;
    align-items: center;
    justify-content: center;
    color: var(--text-primary);
    cursor: pointer;
    transition: all 0.2s ease;
  }
  
  .graph-control-btn:hover {
    background: rgba(15, 23, 42, 0.9);
    transform: translateY(-2px);
  }
  
  .mini-graph-legend {
    position: absolute;
    bottom: 10px;
    left: 15px;
    display: flex;
    gap: 12px;
    z-index: 5;
    background: rgba(15, 23, 42, 0.7);
    padding: 5px 10px;
    border-radius: 20px;
    backdrop-filter: blur(5px);
    border: 1px solid var(--border-primary);
  }
  
  .legend-item {
    display: flex;
    align-items: center;
    gap: 5px;
    font-size: 0.75rem;
    color: var(--text-secondary);
  }
  
  .legend-dot {
    width: 8px;
    height: 8px;
    border-radius: 50%;
    display: inline-block;
  }
  
  .legend-dot.current {
    background-color: #F43F5E;
  }
  
  .legend-dot.post {
    background-color: #3B82F6;
  }
  
  .legend-dot.tag {
    background-color: #10B981;
    transform: rotate(45deg);
    border-radius: 0;
  }
  
  /* Fullscreen container styles - ensuring proper z-index */
  .fullscreen-container {
    position: fixed;
    top: 0;
    left: 0;
    width: 100vw;
    height: 100vh;
    background: var(--bg-primary, #0f172a);
    z-index: 9999;
    display: none;
    overflow: hidden;
  }
  
  .fullscreen-container.active {
    display: flex;
    flex-direction: column;
  }
  
  .fullscreen-header {
    display: flex;
    justify-content: space-between;
    align-items: center;
    padding: 1rem 2rem;
    background: rgba(15, 23, 42, 0.9);
    border-bottom: 1px solid var(--border-primary, #334155);
    z-index: 2;
  }
  
  .fullscreen-title {
    font-size: 1.5rem;
    font-weight: 600;
    color: var(--text-primary, #e2e8f0);
    margin: 0;
  }
  
  .fullscreen-controls {
    display: flex;
    gap: 1rem;
  }
  
  .fullscreen-btn {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    background: rgba(51, 65, 85, 0.7);
    color: var(--text-primary, #e2e8f0);
    border: 1px solid var(--border-primary, #334155);
    padding: 0.5rem 1rem;
    border-radius: 4px;
    cursor: pointer;
    font-size: 0.875rem;
    transition: all 0.2s ease;
  }
  
  .fullscreen-btn:hover {
    background: rgba(51, 65, 85, 0.9);
    border-color: var(--accent-primary, #3b82f6);
  }
  
  .fullscreen-content {
    display: flex;
    height: calc(100vh - 60px);
    overflow: hidden;
  }
  
  .fullscreen-graph {
    width: 70%;
    height: 100%;
    overflow: hidden;
    position: relative;
  }
  
  .fullscreen-article {
    width: 30%;
    height: 100%;
    padding: 1rem;
    overflow-y: auto;
    background: rgba(30, 41, 59, 0.5);
    border-left: 1px solid var(--border-primary, #334155);
    position: relative;
    display: flex;
    flex-direction: column;
  }
  
  .article-placeholder {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    text-align: center;
    height: 100%;
    color: var(--text-secondary, #94a3b8);
    padding: 2rem;
  }
  
  .article-placeholder svg {
    margin-bottom: 1rem;
    opacity: 0.5;
  }
  
  .article-content {
    display: none;
    height: 100%;
    overflow-y: auto;
    padding-right: 1rem;
  }
  
  .article-header {
    margin-bottom: 1.5rem;
    padding-bottom: 1rem;
    border-bottom: 1px solid var(--border-primary, #334155);
  }
  
  .article-header h2 {
    font-size: 1.5rem;
    margin-bottom: 0.75rem;
    color: var(--text-primary, #e2e8f0);
  }
  
  .article-view-link {
    display: inline-flex;
    align-items: center;
    gap: 0.5rem;
    color: var(--accent-primary, #3b82f6);
    text-decoration: none;
    font-size: 0.875rem;
    transition: color 0.2s ease;
  }
  
  .article-view-link:hover {
    color: var(--accent-secondary, #60a5fa);
    text-decoration: underline;
  }
  
  .article-content-preview {
    color: var(--text-secondary, #94a3b8);
    font-size: 0.9375rem;
    line-height: 1.6;
  }
  
  .article-content-preview h1,
  .article-content-preview h2,
  .article-content-preview h3,
  .article-content-preview h4,
  .article-content-preview h5,
  .article-content-preview h6 {
    color: var(--text-primary, #e2e8f0);
    margin-top: 1.5rem;
    margin-bottom: 0.75rem;
  }
  
  .article-content-preview p {
    margin-bottom: 1rem;
  }
  
  .article-content-preview code {
    background: rgba(0, 0, 0, 0.2);
    padding: 0.2rem 0.4rem;
    border-radius: 3px;
    font-family: 'JetBrains Mono', monospace;
    font-size: 0.8125rem;
  }
  
  .article-content-preview pre {
    background: rgba(0, 0, 0, 0.3);
    padding: 1rem;
    border-radius: 6px;
    overflow-x: auto;
    margin: 1rem 0;
  }
  
  .tag-content {
    height: 100%;
    overflow-y: auto;
  }
  
  .tag-posts-list {
    list-style-type: none;
    padding: 0;
    margin: 0;
  }
  
  .tag-post-item {
    margin-bottom: 0.75rem;
  }
  
  .tag-post-link {
    display: block;
    padding: 0.75rem;
    background: rgba(30, 41, 59, 0.5);
    border: 1px solid var(--border-primary, #334155);
    border-radius: 6px;
    color: var(--text-primary, #e2e8f0);
    text-decoration: none;
    transition: all 0.2s ease;
  }
  
  .tag-post-link:hover {
    background: rgba(51, 65, 85, 0.7);
    border-color: var(--accent-primary, #3b82f6);
    transform: translateY(-2px);
  }
  
  .fs-control-btn {
    background: rgba(15, 23, 42, 0.7);
    border: 1px solid var(--border-primary, #334155);
    border-radius: 50%;
    width: 36px;
    height: 36px;
    display: flex;
    align-items: center;
    justify-content: center;
    color: var(--text-primary, #e2e8f0);
    cursor: pointer;
    transition: all 0.2s ease;
    position: absolute;
    z-index: 10;
  }
  
  .fs-control-btn:hover {
    background: rgba(15, 23, 42, 0.9);
    transform: translateY(-2px);
    border-color: var(--accent-primary, #3b82f6);
  }
  
  .fullscreen-graph-controls {
    position: absolute;
    top: 1rem;
    right: 1rem;
    display: flex;
    gap: 0.5rem;
    z-index: 10;
  }
  
  .article-loading {
    display: flex;
    align-items: center;
    justify-content: center;
    height: 100px;
    color: var(--text-secondary, #94a3b8);
    font-size: 0.875rem;
  }
  
  .article-error {
    background: rgba(220, 38, 38, 0.1);
    border: 1px solid rgba(220, 38, 38, 0.2);
    border-radius: 6px;
    padding: 1.5rem;
    color: #ef4444;
    margin-top: 1rem;
  }
  
  .article-error h2 {
    color: #ef4444;
    margin-top: 0;
    margin-bottom: 1rem;
    font-size: 1.25rem;
  }
  
  @media (max-width: 768px) {
    .mini-graph-legend {
      bottom: 5px;
      left: 50%;
      transform: translateX(-50%);
      flex-wrap: wrap;
      justify-content: center;
    }
    
    .physics-controls,
    .graph-controls {
      top: auto;
      bottom: 10px;
      left: 10px;
      right: auto;
    }
    
    .graph-controls {
      right: 10px;
      left: auto;
    }
  }
</style>

<script define:vars={{ graphData, nodeColors, nodeSizes, defaultPhysics }}>
  // Ensure we have a global retry counter
  window.miniGraphRetryCount = window.miniGraphRetryCount || 0;
  const MAX_RETRIES = 5;
  const RETRY_DELAY = 500; // ms
  
  // Initialize on DOMContentLoaded
  document.addEventListener('DOMContentLoaded', function() {
    initializeMiniGraph();
  });
  
  // Also check for window load, sometimes DOMContentLoaded is too early
  window.addEventListener('load', function() {
    // If we already have a Cytoscape instance, don't reinitialize
    if (!window.miniCy) {
      initializeMiniGraph();
    }
  });
  
  function initializeMiniGraph() {
    // Set a timeout to ensure the DOM has had time to render properly
    setTimeout(loadCytoscape, 100);
  }
  
  function loadCytoscape() {
    // Get elements
    const container = document.getElementById('mini-cy');
    const errorEl = document.getElementById('mini-graph-error');
    const loadingEl = document.getElementById('mini-graph-loading');
    
    // Reset error display
    if (errorEl) errorEl.style.display = 'none';
    
    // Show loading indicator
    if (loadingEl) loadingEl.style.display = 'flex';
    
    // Check if container exists and is visible
    if (!container) {
      displayError('Graph container not found.');
      return;
    }
    
    // Check container dimensions to ensure it's visible in the DOM
    if (container.offsetWidth === 0 || container.offsetHeight === 0) {
      if (window.miniGraphRetryCount < MAX_RETRIES) {
        window.miniGraphRetryCount++;
        console.log(`Container has zero size, retrying in ${RETRY_DELAY}ms (attempt ${window.miniGraphRetryCount}/${MAX_RETRIES})`);
        setTimeout(loadCytoscape, RETRY_DELAY);
        return;
      } else {
        displayError('Graph container has zero dimensions after multiple retries.');
        return;
      }
    }
    
    // Check if the Cytoscape library is loaded
    if (typeof cytoscape === 'undefined') {
      displayError('Cytoscape library not loaded');
      
      // Try to load it dynamically as a fallback
      const script = document.createElement('script');
      script.src = 'https://unpkg.com/cytoscape@3.25.0/dist/cytoscape.min.js';
      script.onload = () => {
        // Also load the layout extension
        const layoutScript = document.createElement('script');
        layoutScript.src = 'https://unpkg.com/cytoscape-cose-bilkent@4.1.0/cytoscape-cose-bilkent.js';
        layoutScript.onload = () => {
          // Reset counter and try again
          window.miniGraphRetryCount = 0;
          loadCytoscape();
        };
        document.head.appendChild(layoutScript);
      };
      document.head.appendChild(script);
      return;
    }
    
    // Register layout if needed
    if (typeof cytoscape.layouts['cose-bilkent'] === 'undefined' && typeof window.cytoscapeCoseBilkent !== 'undefined') {
      cytoscape.use(window.cytoscapeCoseBilkent);
    }
    
    // Get graph data from window (set by server-side render)
    const graphData = window.miniGraphData;
    
    if (!graphData || !graphData.nodes || !graphData.edges) {
      displayError("Graph data is missing or invalid");
      return;
    }
    
    // Only proceed if we have nodes to display
    if (graphData.nodes.length === 0) {
      displayError("No nodes to display in graph");
      return;
    }
    
    // Format data for Cytoscape
    const elements = {
      nodes: graphData.nodes.map(node => ({
        data: { 
          id: node.id, 
          label: node.label,
          type: node.type,
          url: node.url || '',
          slug: node.slug || ''
        }
      })),
      edges: graphData.edges.map(edge => ({
        data: { 
          id: edge.id,
          source: edge.source, 
          target: edge.target,
          type: edge.type || 'default'
        }
      }))
    };
    
    // Hide loading indicator when graph is ready
    if (loadingEl) loadingEl.style.display = 'none';
    
    // Initialize Cytoscape with improved Obsidian-like styling
    try {
      const cy = cytoscape({
        container: container,
        elements: elements,
        style: [
          // Base node styles
          { selector: 'node', style: { 
              'background-color': 'data(color)', 
              'label': 'data(label)', 
              'width': 'data(size)', 
              'height': 'data(size)', 
              'font-size': '10px', 
              'color': '#E2E8F0', 
              'text-valign': 'bottom', 
              'text-halign': 'center', 
              'text-margin-y': '7px', 
              'text-background-opacity': 0.7, 
              'text-background-color': '#0F1219', 
              'text-background-padding': '3px', 
              'text-background-shape': 'roundrectangle', 
              'text-max-width': '120px', 
              'text-wrap': 'ellipsis', 
              'border-width': '2px', 
              'border-color': '#0F1219', 
              'border-opacity': 0.8, 
              'z-index': 10,
              'transition-property': 'background-color, border-color, border-width',
              'transition-duration': '0.2s'
          }},
          // Post node specific styles
          { selector: 'node[type="post"]', style: {
              'shape': 'ellipse'
          }},
          // Tag node specific styles
          { selector: 'node[type="tag"]', style: {
              'shape': 'diamond'
          }},
          // Current post node styles
          { selector: 'node[type="current"]', style: {
              'shape': 'ellipse',
              'border-width': '3px',
              'border-color': '#F43F5E',
              'border-opacity': 1,
              'text-outline-width': 1,
              'text-outline-color': '#000'
          }},
          // Edge styles
          { selector: 'edge', style: { 
              'width': 'mapData(weight, 1, 10, 1, 3)', 
              'line-color': 'rgba(226, 232, 240, 0.3)', 
              'curve-style': 'bezier', 
              'z-index': 1,
              'transition-property': 'line-color, width',
              'transition-duration': '0.2s'
          }},
          // Post-tag edge specific styles
          { selector: 'edge[type="post-tag"]', style: {
              'line-color': 'rgba(16, 185, 129, 0.45)'
          }},
          // Post-post edge specific styles
          { selector: 'edge[type="post-post"]', style: {
              'line-color': 'rgba(59, 130, 246, 0.45)'
          }},
          // Selection and hover styles
          { selector: '.highlighted', style: { 
              'background-color': 'data(color)', 
              'border-color': '#FFFFFF', 
              'border-width': '3px', 
              'color': '#FFFFFF', 
              'text-background-opacity': 0.9,
              'z-index': 20
          }},
          { selector: '.faded', style: { 
              'opacity': 0.2, 
              'text-opacity': 0.3, 
              'background-opacity': 0.3,
              'z-index': 1
          }},
          { selector: ':selected', style: {
              'border-width': '4px',
              'border-color': '#FFFFFF',
              'z-index': 30
          }}
        ],
        // Initial layout with Obsidian-like settings
        layout: getLayoutOptions(),
        zoom: 1,
        minZoom: 0.1,
        maxZoom: 3,
        zoomingEnabled: true,
        userZoomingEnabled: true,
        panningEnabled: true,
        userPanningEnabled: true,
        boxSelectionEnabled: false,
        autoungrabify: false,
        wheelSensitivity: 0.3,
      });
      
      // Store a reference to the Cytoscape instance
      window.miniCy = cy;
      
      // Add enhanced Obsidian-like dragging behavior
      cy.on('drag', 'node', function(evt) {
        const node = evt.target;
        node.lock(); // Lock node during drag to prevent physics interference
        
        // Get immediate neighbors (directly connected nodes)
        const directNeighbors = node.neighborhood('node');
        
        // Apply a gentle pull to immediate neighbors
        if (directNeighbors.length > 0) {
          directNeighbors.forEach(neighbor => {
            // Don't move nodes that are being manually dragged
            if (!neighbor.grabbed()) {
              // Calculate distance and direction
              const dx = node.position('x') - neighbor.position('x');
              const dy = node.position('y') - neighbor.position('y');
              const distance = Math.sqrt(dx*dx + dy*dy);
              
              // Apply stronger pull factor for more momentum
              const pullFactor = Math.min(defaultPhysics.pullStrength * 3, 30/distance);
              neighbor.position({
                x: neighbor.position('x') + dx * pullFactor,
                y: neighbor.position('y') + dy * pullFactor
              });
            }
          });
        }
        
        // Store velocity for momentum effect
        if (!node.scratch('_dragVelocity')) {
          node.scratch('_dragVelocity', { x: 0, y: 0 });
          node.scratch('_prevPos', { x: node.position('x'), y: node.position('y') });
        }
        
        // Calculate current velocity
        const prevPos = node.scratch('_prevPos');
        const velocity = {
          x: node.position('x') - prevPos.x,
          y: node.position('y') - prevPos.y
        };
        
        // Store current position and velocity
        node.scratch('_prevPos', { x: node.position('x'), y: node.position('y') });
        node.scratch('_dragVelocity', velocity);
      });
      
      cy.on('dragfree', 'node', function(evt) {
        const node = evt.target;
        
        // Apply momentum effect
        const velocity = node.scratch('_dragVelocity');
        if (velocity && (Math.abs(velocity.x) > 0.1 || Math.abs(velocity.y) > 0.1)) {
          // Damping factor
          const damping = 0.95;
          const decayFrames = 10;
          
          // Apply momentum with decay
          let frame = 0;
          const applyMomentum = () => {
            if (frame >= decayFrames) {
              node.unlock();
              return;
            }
            
            // Apply momentum with decay
            const decayFactor = 1 - (frame / decayFrames);
            
            // Move the node
            node.position({
              x: node.position('x') + velocity.x * damping * decayFactor,
              y: node.position('y') + velocity.y * damping * decayFactor
            });
            
            // Also move connected nodes for a nicer effect
            const neighbors = node.neighborhood('node');
            neighbors.forEach(neighbor => {
              if (!neighbor.grabbed() && !neighbor.locked()) {
                // Calculate distance and direction
                const dx = node.position('x') - neighbor.position('x');
                const dy = node.position('y') - neighbor.position('y');
                const distance = Math.sqrt(dx*dx + dy*dy);
                
                // Apply pull factor based on distance
                const pullFactor = Math.min(defaultPhysics.pullStrength * 2, 20/distance) * decayFactor;
                neighbor.position({
                  x: neighbor.position('x') + dx * pullFactor,
                  y: neighbor.position('y') + dy * pullFactor
                });
              }
            });
            
            frame++;
            requestAnimationFrame(applyMomentum);
          };
          
          requestAnimationFrame(applyMomentum);
        } else {
          setTimeout(() => {
            node.unlock(); // Unlock after drag complete
          }, 50);
        }
      });
      
      // Add node hover effects
      cy.on('mouseover', 'node', function(e) {
        const node = e.target;
        node.addClass('highlighted');
        node.connectedEdges().addClass('highlighted');
        container.style.cursor = 'pointer';
      });
      
      cy.on('mouseout', 'node', function(e) {
        const node = e.target;
        node.removeClass('highlighted');
        node.connectedEdges().removeClass('highlighted');
        container.style.cursor = 'default';
      });
      
      // Add node selection effects
      cy.on('tap', 'node', function(e) {
        const node = e.target;
        const nodeData = node.data();
        
        // Highlight the selected node and its connections
        cy.elements().removeClass('highlighted').addClass('faded');
        node.removeClass('faded').addClass('highlighted');
        node.neighborhood().removeClass('faded').addClass('highlighted');
        
        // Navigate to the node URL when clicked
        if (nodeData.url && nodeData.url !== '#') {
          window.location.href = nodeData.url;
        }
      });
      
      // Reset highlighting when clicking on background
      cy.on('tap', function(e) {
        if (e.target === cy) {
          cy.elements().removeClass('highlighted faded');
          cy.elements().unselect();
        }
      });
      
      // Connect physics controls
      initPhysicsControls();
      
      // Connect graph controls
      initGraphControls(cy);
      
      // Initialize fullscreen mode
      initFullscreenMode(cy);
      
      // Make the graph responsive
      window.addEventListener('resize', () => {
        if (cy) {
          cy.resize();
          cy.fit();
        }
      });
      
      // Function to get layout options based on current physics settings
      function getLayoutOptions() {
        const available = typeof cytoscape.layouts['cose-bilkent'] !== 'undefined';
        
        if (available) {
          return {
            name: 'cose-bilkent',
            animate: defaultPhysics.animate,
            randomize: false,
            fit: true,
            padding: 10,
            nodeRepulsion: defaultPhysics.nodeRepulsion,
            idealEdgeLength: 80,
            edgeElasticity: defaultPhysics.edgeElasticity,
            nestingFactor: 0.1,
            gravity: defaultPhysics.gravity,
            numIter: 2500,
            initialEnergyOnIncremental: 0.3,
            refresh: 10,
            tile: false
          };
        } else {
          // Fallback to standard cose layout
          return {
            name: 'cose',
            animate: defaultPhysics.animate,
            randomize: false,
            fit: true,
            padding: 10,
            nodeRepulsion: function() { 
              return defaultPhysics.nodeRepulsion;
            },
            edgeElasticity: function() {
              return defaultPhysics.edgeElasticity * 100;
            },
            gravity: defaultPhysics.gravity,
            numIter: 2500,
            initialTemp: 200,
            coolingFactor: 0.95,
            minTemp: 1
          };
        }
      }
      
      // Initialize physics control panel
      function initPhysicsControls() {
        const toggleBtn = document.getElementById('toggle-physics-panel');
        const physicsPanel = document.getElementById('physics-panel');
        const applyBtn = document.getElementById('apply-physics');
        const resetBtn = document.getElementById('reset-physics');
        
        const repulsionSlider = document.getElementById('node-repulsion');
        const repulsionValue = document.getElementById('repulsion-value');
        const elasticitySlider = document.getElementById('edge-elasticity');
        const elasticityValue = document.getElementById('elasticity-value');
        const gravitySlider = document.getElementById('gravity');
        const gravityValue = document.getElementById('gravity-value');
        
        // Set initial values
        if (repulsionSlider) repulsionSlider.value = defaultPhysics.nodeRepulsion;
        if (repulsionValue) repulsionValue.textContent = defaultPhysics.nodeRepulsion;
        if (elasticitySlider) elasticitySlider.value = defaultPhysics.edgeElasticity;
        if (elasticityValue) elasticityValue.textContent = defaultPhysics.edgeElasticity;
        if (gravitySlider) gravitySlider.value = defaultPhysics.gravity;
        if (gravityValue) gravityValue.textContent = defaultPhysics.gravity;
        
        // Toggle physics panel
        if (toggleBtn && physicsPanel) {
          toggleBtn.addEventListener('click', () => {
            physicsPanel.classList.toggle('active');
          });
        }
        
        // Update values as sliders change
        if (repulsionSlider) {
          repulsionSlider.addEventListener('input', (e) => {
            repulsionValue.textContent = e.target.value;
          });
        }
        
        if (elasticitySlider) {
          elasticitySlider.addEventListener('input', (e) => {
            elasticityValue.textContent = e.target.value;
          });
        }
        
        if (gravitySlider) {
          gravitySlider.addEventListener('input', (e) => {
            gravityValue.textContent = e.target.value;
          });
        }
        
        // Apply physics changes
        if (applyBtn) {
          applyBtn.addEventListener('click', () => {
            defaultPhysics.nodeRepulsion = parseFloat(repulsionSlider.value);
            defaultPhysics.edgeElasticity = parseFloat(elasticitySlider.value);
            defaultPhysics.gravity = parseFloat(gravitySlider.value);
            
            // Run layout with new settings
            cy.layout(getLayoutOptions()).run();
            
            // Close panel after applying
            physicsPanel.classList.remove('active');
          });
        }
        
        // Reset physics to defaults
        if (resetBtn) {
          resetBtn.addEventListener('click', () => {
            defaultPhysics = { ...defaultPhysics };
            
            // Update UI
            repulsionSlider.value = defaultPhysics.nodeRepulsion;
            repulsionValue.textContent = defaultPhysics.nodeRepulsion;
            elasticitySlider.value = defaultPhysics.edgeElasticity;
            elasticityValue.textContent = defaultPhysics.edgeElasticity;
            gravitySlider.value = defaultPhysics.gravity;
            gravityValue.textContent = defaultPhysics.gravity;
            
            // Run layout with default settings
            cy.layout(getLayoutOptions()).run();
          });
        }
      }
      
      // Initialize graph control buttons
      function initGraphControls(cy) {
        const resetViewBtn = document.getElementById('reset-view');
        const zoomInBtn = document.getElementById('zoom-in');
        const zoomOutBtn = document.getElementById('zoom-out');
        const fullscreenBtn = document.getElementById('fullscreen');
        
        if (resetViewBtn) {
          resetViewBtn.addEventListener('click', () => {
            // Reset zoom and position
            cy.animate({
              zoom: 1,
              center: {
                eles: cy.elements()
              }
            }, {
              duration: 500
            });
            
            // Reset selection and highlighting
            cy.elements().removeClass('highlighted faded');
            cy.elements().unselect();
          });
        }
        
        if (zoomInBtn) {
          zoomInBtn.addEventListener('click', () => {
            cy.zoom(cy.zoom() * 1.2);
            cy.center();
          });
        }
        
        if (zoomOutBtn) {
          zoomOutBtn.addEventListener('click', () => {
            cy.zoom(cy.zoom() / 1.2);
            cy.center();
          });
        }
        
        if (fullscreenBtn) {
          fullscreenBtn.addEventListener('click', () => {
            const fullscreenContainer = document.getElementById('fullscreen-container');
            if (fullscreenContainer) {
              fullscreenContainer.classList.add('active');
              document.body.classList.add('graph-fullscreen-mode');
              // Initialize fullscreen graph after a short delay
              setTimeout(() => {
                initFullscreenGraph(cy);
              }, 100);
            }
          });
        }
      }
      
      // Initialize fullscreen mode
      function initFullscreenMode(cy) {
        const exitBtn = document.getElementById('exit-fullscreen');
        const fullscreenContainer = document.getElementById('fullscreen-container');
        
        if (exitBtn && fullscreenContainer) {
          exitBtn.addEventListener('click', () => {
            fullscreenContainer.classList.remove('active');
            document.body.classList.remove('graph-fullscreen-mode');
          });
        }
      }
      
      // Function to set up fullscreen graph with content preview
      function initFullscreenGraph(cy) {
        const graphContainer = document.getElementById('fullscreen-graph-container');
        const articlePlaceholder = document.getElementById('article-placeholder');
        const articleContent = document.getElementById('article-content');
        let fullscreenCy = null;
        
        if (!graphContainer) return;
        
        // Create a new Cytoscape instance for fullscreen mode
        fullscreenCy = cytoscape({
          container: graphContainer,
          elements: cy.json().elements,
          style: cy.style().json(),
          layout: getLayoutOptions(), // Use layout with physics settings
          zoom: 1.2, // Start a bit zoomed in for better visibility
          minZoom: 0.1,
          maxZoom: 4,
          wheelSensitivity: 0.3
        });
        
        // Add the same interactions
        fullscreenCy.on('mouseover', 'node', function(e) {
          const node = e.target;
          node.addClass('highlighted');
          node.connectedEdges().addClass('highlighted');
          graphContainer.style.cursor = 'pointer';
        });
        
        fullscreenCy.on('mouseout', 'node', function(e) {
          const node = e.target;
          node.removeClass('highlighted');
          node.connectedEdges().removeClass('highlighted');
          graphContainer.style.cursor = 'default';
        });
        
        // Add node selection with content preview
        fullscreenCy.on('tap', 'node', function(e) {
          const node = e.target;
          const nodeData = node.data();
          
          // Highlight the selected node and its connections
          fullscreenCy.elements().removeClass('highlighted').addClass('faded');
          node.removeClass('faded').addClass('highlighted');
          node.neighborhood().removeClass('faded').addClass('highlighted');
          
          // Load content preview if it's a post
          if (nodeData.type === 'post' || nodeData.type === 'current') {
            if (articlePlaceholder) articlePlaceholder.style.display = 'none';
            if (articleContent) {
              articleContent.style.display = 'block';
              articleContent.innerHTML = '<div class="article-loading">Loading content...</div>';
              
              // Fetch and display article content
              fetch(nodeData.url)
                .then(response => response.text())
                .then(html => {
                  const parser = new DOMParser();
                  const doc = parser.parseFromString(html, 'text/html');
                  
                  // Extract article content (adjust selectors as needed)
                  const articleElement = doc.querySelector('.blog-post-content') || 
                                          doc.querySelector('article') || 
                                          doc.querySelector('.prose') || 
                                          doc.querySelector('main');
                  
                  const title = doc.querySelector('.blog-post-title')?.textContent || nodeData.label;
                  
                  if (articleElement) {
                    // Create article header
                    const articleHeader = document.createElement('div');
                    articleHeader.className = 'article-header';
                    articleHeader.innerHTML = `
                      <h2>${title}</h2>
                      <a href="${nodeData.url}" class="article-view-link" target="_self">
                        <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
                          <path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6"></path>
                          <polyline points="15 3 21 3 21 9"></polyline>
                          <line x1="10" y1="14" x2="21" y2="3"></line>
                        </svg>
                        View Full Article
                      </a>
                    `;
                    
                    // Set content
                    articleContent.innerHTML = '';
                    articleContent.appendChild(articleHeader);
                    
                    // Create a copy of the article content to avoid modifying the original DOM
                    const contentClone = articleElement.cloneNode(true);
                    
                    // Style adjustments for better preview rendering
                    const contentWrapper = document.createElement('div');
                    contentWrapper.className = 'article-content-preview';
                    contentWrapper.appendChild(contentClone);
                    
                    articleContent.appendChild(contentWrapper);
                  } else {
                    articleContent.innerHTML = `
                      <div class="article-header">
                        <h2>${title}</h2>
                        <a href="${nodeData.url}" class="article-view-link" target="_self">
                          <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
                            <path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6"></path>
                            <polyline points="15 3 21 3 21 9"></polyline>
                            <line x1="10" y1="14" x2="21" y2="3"></line>
                          </svg>
                          View Full Article
                        </a>
                      </div>
                      <div class="article-content-preview">
                        <p>Sorry, couldn't extract article content. Click the link above to view the full article.</p>
                      </div>
                    `;
                  }
                })
                .catch(error => {
                  console.error('Error loading article:', error);
                  articleContent.innerHTML = `
                    <div class="article-error">
                      <h2>Error Loading Content</h2>
                      <p>There was a problem loading the article preview</p>
                      <a href="${nodeData.url}" class="article-view-link" target="_self">
                        Open Article
                      </a>
                    </div>
                  `;
                });
            }
          } else if (nodeData.type === 'tag') {
            // For tag nodes, show related posts
            if (articlePlaceholder) articlePlaceholder.style.display = 'none';
            if (articleContent) {
              articleContent.style.display = 'block';
              
              // Get connected posts
              const connectedPosts = node.neighborhood('node[type="post"], node[type="current"]');
              
              articleContent.innerHTML = `
                <div class="tag-content">
                  <div class="article-header">
                    <h2>Posts tagged with: #${nodeData.label}</h2>
                  </div>
                  <ul class="tag-posts-list">
                    ${connectedPosts.length === 0 
                      ? '<li>No posts found with this tag</li>' 
                      : connectedPosts.map(post => {
                          const postData = post.data();
                          return `
                            <li class="tag-post-item">
                              <a href="${postData.url}" class="tag-post-link" data-node-id="${postData.id}">
                                ${postData.label}
                              </a>
                            </li>
                          `;
                        }).map(html => html).join('')
                    }
                  </ul>
                </div>
              `;
              
              // Add click events for post links
              setTimeout(() => {
                document.querySelectorAll('.tag-post-link').forEach(link => {
                  link.addEventListener('click', (e) => {
                    e.preventDefault();
                    const nodeId = link.getAttribute('data-node-id');
                    const postNode = fullscreenCy.getElementById(nodeId);
                    if (postNode.length > 0) {
                      postNode.trigger('tap');
                    } else {
                      window.location.href = link.href;
                    }
                  });
                });
              }, 100);
            }
          }
        });
        
        // Reset highlighting when clicking on background
        fullscreenCy.on('tap', function(e) {
          if (e.target === fullscreenCy) {
            fullscreenCy.elements().removeClass('highlighted faded');
            fullscreenCy.elements().unselect();
            
            // Reset article view
            if (articlePlaceholder) articlePlaceholder.style.display = 'flex';
            if (articleContent) {
              articleContent.style.display = 'none';
              articleContent.innerHTML = '';
            }
          }
        });
        
        // Add enhanced Obsidian-like dragging behavior for fullscreen view
        fullscreenCy.on('drag', 'node', function(evt) {
          const node = evt.target;
          node.lock(); // Lock node during drag to prevent physics interference
          
          // Get immediate neighbors (directly connected nodes)
          const directNeighbors = node.neighborhood('node');
          
          // Apply a gentle pull to immediate neighbors
          if (directNeighbors.length > 0) {
            directNeighbors.forEach(neighbor => {
              // Don't move nodes that are being manually dragged
              if (!neighbor.grabbed()) {
                // Calculate distance and direction
                const dx = node.position('x') - neighbor.position('x');
                const dy = node.position('y') - neighbor.position('y');
                const distance = Math.sqrt(dx*dx + dy*dy);
                
                // Apply gentle force (stronger for closer nodes)
                const pullFactor = Math.min(defaultPhysics.pullStrength, 15/distance);
                neighbor.position({
                  x: neighbor.position('x') + dx * pullFactor,
                  y: neighbor.position('y') + dy * pullFactor
                });
              }
            });
          }
        });
        
        fullscreenCy.on('dragfree', 'node', function(evt) {
          const node = evt.target;
          setTimeout(() => {
            node.unlock(); // Unlock after drag complete
          }, 50);
        });
        
        // Add fullscreen-specific controls
        const fullscreenControls = document.createElement('div');
        fullscreenControls.className = 'fullscreen-graph-controls';
        fullscreenControls.innerHTML = `
          <button id="fs-reset-view" class="fs-control-btn" aria-label="Reset view">
            <svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M3 2v6h6"></path><path d="M21 12A9 9 0 0 0 6 5.3L3 8"></path><path d="M21 22v-6h-6"></path><path d="M3 12a9 9 0 0 0 15 6.7l3-2.7"></path></svg>
          </button>
          <button id="fs-zoom-in" class="fs-control-btn" aria-label="Zoom in">
            <svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="11" cy="11" r="8"></circle><line x1="21" y1="21" x2="16.65" y2="16.65"></line><line x1="11" y1="8" x2="11" y2="14"></line><line x1="8" y1="11" x2="14" y2="11"></line></svg>
          </button>
          <button id="fs-zoom-out" class="fs-control-btn" aria-label="Zoom out">
            <svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="11" cy="11" r="8"></circle><line x1="21" y1="21" x2="16.65" y2="16.65"></line><line x1="8" y1="11" x2="14" y2="11"></line></svg>
          </button>
        `;
        
        graphContainer.appendChild(fullscreenControls);
        
        // Connect fullscreen controls
        document.getElementById('fs-reset-view')?.addEventListener('click', () => {
          fullscreenCy.fit(undefined, 20);
          fullscreenCy.elements().removeClass('highlighted faded');
          fullscreenCy.elements().unselect();
          
          // Reset article view
          if (articlePlaceholder) articlePlaceholder.style.display = 'flex';
          if (articleContent) {
            articleContent.style.display = 'none';
            articleContent.innerHTML = '';
          }
        });
        
        document.getElementById('fs-zoom-in')?.addEventListener('click', () => {
          fullscreenCy.zoom(fullscreenCy.zoom() * 1.2);
          fullscreenCy.center();
        });
        
        document.getElementById('fs-zoom-out')?.addEventListener('click', () => {
          fullscreenCy.zoom(fullscreenCy.zoom() / 1.2);
          fullscreenCy.center();
        });
        
        // Store reference to fullscreen cytoscape instance
        window.fullscreenMiniGraphCy = fullscreenCy;
        
        // Run a layout to spread the nodes nicely
        setTimeout(() => {
          fullscreenCy.layout(getLayoutOptions()).run();
        }, 200);
      }
    } catch (error) {
      displayError(`Error initializing graph: ${error.message}`);
      console.error('Cytoscape initialization error:', error);
    }
  }
  
  function displayError(message) {
    const errorEl = document.getElementById('mini-graph-error');
    const loadingEl = document.getElementById('mini-graph-loading');
    
    if (errorEl) {
      errorEl.textContent = message;
      errorEl.style.display = 'block';
    }
    
    if (loadingEl) {
      loadingEl.style.display = 'none';
    }
    
    console.error(`MiniKnowledgeGraph: ${message}`);
  }
</script>

<script>
  // Add toggle functionality for physics panel and info panel
  document.addEventListener('DOMContentLoaded', function() {
    // Physics panel toggle
    const toggleBtn = document.getElementById('toggle-physics-panel');
    const physicsPanel = document.getElementById('physics-panel');
    
    if (toggleBtn && physicsPanel) {
      toggleBtn.addEventListener('click', (e) => {
        e.stopPropagation();
        physicsPanel.classList.toggle('active');
      });
    }
    
    // Info panel toggle
    const infoBtn = document.getElementById('graph-info-button');
    const infoPanel = document.getElementById('graph-info-panel');
    
    if (infoBtn && infoPanel) {
      infoBtn.addEventListener('click', (e) => {
        e.stopPropagation();
        infoPanel.classList.toggle('active');
      });
    }
    
    // Close panels when clicking outside
    document.addEventListener('click', (e) => {
      if (physicsPanel && !physicsPanel.contains(e.target) && e.target !== toggleBtn) {
        physicsPanel.classList.remove('active');
      }
      
      if (infoPanel && !infoPanel.contains(e.target) && e.target !== infoBtn) {
        infoPanel.classList.remove('active');
      }
    });
  });
</script>