---
// MiniGraph.astro - A standalone mini knowledge graph component with fullscreen capability
// This component is designed to work independently from the blog structure and now includes content previews

// Define props interface
interface Props {
  slug: string;        // Current post slug
  title: string;       // Current post title
  tags?: string[];     // Current post tags
  category?: string;   // Current post category
  content?: string;    // Current post content HTML
  relatedPosts?: any[]; // Related posts data with their content
  allPosts?: any[];    // All posts for second level relationships
  width?: string;      // Optional width parameter
}

// Extract props with defaults
const {
  slug,
  title,
  tags = [],
  category = "Uncategorized",
  content = "", // Add content property for current post
  relatedPosts = [],
  allPosts = [],
  width = "100%" // Default width of the component
} = Astro.props;

// Generate unique ID for the graph container
const graphId = `graph-${Math.random().toString(36).substring(2, 8)}`;

// Get all unique tags from related posts (Level 1 tags)
const relatedPostsTags = relatedPosts
  .flatMap(post => post.data.tags || [])
  .filter(tag => !tags.includes(tag)); // Exclude current post tags to avoid duplicates

// Create a set of all Level 1 nodes' tags for filtering Level 2 tags
const level1TagsSet = new Set([...tags, ...relatedPostsTags]);

// Get Level 2 posts: posts related to Level 1 tags (excluding current post and Level 1 posts)
const level2PostIds = new Set();
const level2Posts = [];

// For each Level 1 tag, find related posts
tags.forEach(tag => {
  allPosts.forEach(post => {
    // Skip if post is current post or already in related posts
    if (post.slug === slug || relatedPosts.some(rp => rp.slug === post.slug) || level2PostIds.has(post.slug)) {
      return;
    }

    // If post has the tag, add it to Level 2
    if (post.data.tags?.includes(tag)) {
      level2PostIds.add(post.slug);
      level2Posts.push(post);
    }
  });
});

// Only collect Level 2 tags that are directly linked to Level 1 posts
// This fixes the issue where unrelated Level 2 tags were being included
const level2Tags = new Set();
relatedPosts.forEach(post => {
  (post.data.tags || []).forEach(tag => {
    // Only add if not already in Level 0 or Level 1 tags
    if (!tags.includes(tag) && !relatedPostsTags.includes(tag)) {
      level2Tags.add(tag);
    }
  });
});

// Prepare nodes data with different levels
const nodes = [
  // Level 0: Current post node
  {
    id: slug,
    label: title,
    type: "post",
    level: 0,
    category: category,
    tags: tags,
    content: content, // Add content for the current post (as HTML string)
    url: `/posts/${slug}/`
  },
  // Level 1: Tag nodes
  ...tags.map(tag => ({
    id: `tag-${tag}`,
    label: tag,
    type: "tag",
    level: 1,
    url: `/tag/${tag}/`
  })),
  // Level 1: Related post nodes
  ...relatedPosts.map(post => {
    // Extract content from post object - this will vary based on your data structure
    // Try multiple properties where content might be stored
    let postContent = '';
    if (post.data.content) {
      postContent = post.data.content;
    } else if (post.data.body) {
      postContent = post.data.body;
    } else if (post.data.html) {
      postContent = post.data.html;
    } else if (post.content) {
      postContent = post.content;
    } else if (post.body) {
      postContent = post.body;
    } else if (post.html) {
      postContent = post.html;
    } else if (post.data.excerpt) {
      postContent = post.data.excerpt;
    }

    return {
      id: post.slug,
      label: post.data.title,
      type: "post",
      level: 1,
      category: post.data.category || "Uncategorized",
      tags: post.data.tags || [],
      content: postContent, // Add content as HTML string
      url: `/posts/${post.slug}/`
    };
  }),
  // Level 2: Related tags nodes (Tags from Level 1 posts)
  ...relatedPostsTags.map(tag => ({
    id: `tag-${tag}`,
    label: tag,
    type: "tag",
    level: 2,
    url: `/tag/${tag}/`
  })),
  // Level 2: Posts related to tags (Posts connected to Level 1 tags)
  ...level2Posts.map(post => {
    // Extract content from level 2 posts
    let postContent = '';
    if (post.data.content) {
      postContent = post.data.content;
    } else if (post.data.body) {
      postContent = post.data.body;
    } else if (post.data.html) {
      postContent = post.data.html;
    } else if (post.content) {
      postContent = post.content;
    } else if (post.body) {
      postContent = post.body;
    } else if (post.html) {
      postContent = post.html;
    } else if (post.data.excerpt) {
      postContent = post.data.excerpt;
    }

    return {
      id: post.slug,
      label: post.data.title,
      type: "post",
      level: 2,
      category: post.data.category || "Uncategorized",
      tags: post.data.tags || [],
      content: postContent, // Add content as HTML string
      url: `/posts/${post.slug}/`
    };
  }),
  // Level 2: Tags from Level 1 posts (only tags directly connected to Level 1 posts)
  // This was the corrected logic for level2Tags Set
  ...[...level2Tags].map(tag => ({
    id: `tag-${tag}`,
    label: tag.toString(),
    type: "tag",
    level: 2,
    url: `/tag/${tag.toString()}/`
  }))
];

// Create edges connecting nodes
const edges = [
  // Level 0 to Level 1: Current post to its tags
  ...tags.map(tag => ({
    source: slug,
    target: `tag-${tag}`,
    type: "post-tag"
  })),
  // Level 0 to Level 1: Current post to related posts
  ...relatedPosts.map(post => ({
    source: slug,
    target: post.slug,
    type: "post-related"
  })),
  // Level 1 to Level 2: Related posts to their tags
  ...relatedPosts.flatMap(post =>
    (post.data.tags || []).map(tag => ({
      source: post.slug,
      target: `tag-${tag}`,
      type: "post-tag" // Re-using post-tag type for simplicity
    }))
  ),
  // Level 1 to Level 2: Tags to related posts
  ...level2Posts.flatMap(post =>
    tags.filter(tag => post.data.tags?.includes(tag)).map(tag => ({
      source: `tag-${tag}`,
      target: post.slug,
      type: "tag-post" // New type for tag -> post connection
    }))
  )
];


// Prepare graph data object
const graphData = { nodes, edges };

// Define colors for categories
const predefinedColors = {
  'Kubernetes': '#326CE5', 'Docker': '#2496ED', 'DevOps': '#FF6F61',
  'Homelab': '#06B6D4', 'Networking': '#9333EA', 'Infrastructure': '#10B981',
  'Automation': '#F59E0B', 'Security': '#EF4444', 'Monitoring': '#6366F1',
  'Storage': '#8B5CF6', 'Obsidian': '#7C3AED', 'Tutorial': '#3B82F6',
  'Uncategorized': '#A0AEC0'
};
---

<!-- Enhanced HTML structure with specified width -->
<div class="knowledge-graph-wrapper" style={`width: ${width};`}>
  <h4 class="graph-title">Post Connections</h4>
  <div id={graphId} class="mini-graph-container"></div>

  <!-- Fullscreen Toggle Button -->
  <button id={`${graphId}-fullscreen-toggle`} class="fullscreen-toggle" aria-label="Toggle fullscreen">
    <svg id={`${graphId}-fullscreen-enter`} class="fullscreen-icon" 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">
      <polyline points="15 3 21 3 21 9"></polyline>
      <polyline points="9 21 3 21 3 15"></polyline>
      <line x1="21" y1="3" x2="14" y2="10"></line>
      <line x1="3" y1="21" x2="10" y2="14"></line>
    </svg>
    <svg id={`${graphId}-fullscreen-exit`} class="fullscreen-icon hidden" 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">
      <polyline points="4 14 10 14 10 20"></polyline>
      <polyline points="20 10 14 10 14 4"></polyline>
      <line x1="14" y1="10" x2="21" y2="3"></line>
      <line x1="3" y1="21" x2="10" y2="14"></line>
    </svg>
  </button>
</div>

<!-- Fullscreen container (initially hidden) with enhanced info panel -->
<div id={`${graphId}-fullscreen`} class="fullscreen-container">
  <div class="fullscreen-header">
    <div class="fullscreen-title">Knowledge Graph</div>
    <button id={`${graphId}-exit-fullscreen`} class="exit-fullscreen" aria-label="Exit fullscreen">
      <svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
        <line x1="18" y1="6" x2="6" y2="18"></line>
        <line x1="6" y1="6" x2="18" y2="18"></line>
      </svg>
    </button>
  </div>

  <div class="fullscreen-content">
    <!-- Fullscreen graph container -->
    <div id={`${graphId}-fullscreen-graph`} class="fullscreen-graph"></div>

    <!-- Enhanced Info panel with content preview capability -->
    <div id={`${graphId}-info-panel`} class="info-panel">
      <div class="info-panel-header">
        <h3 id={`${graphId}-info-title`} class="info-title">Node Info</h3>
        <button id={`${graphId}-close-info`} class="close-info" aria-label="Close info panel">
          <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">
            <line x1="18" y1="6" x2="6" y2="18"></line>
            <line x1="6" y1="6" x2="18" y2="18"></line>
          </svg>
        </button>
      </div>

      <div class="info-content">
        <div class="info-type">
          <span class="info-label">Type:</span>
          <span id={`${graphId}-info-type`} class="info-value type-value">Post</span>
        </div>

        <div id={`${graphId}-info-category-container`} class="info-category">
          <span class="info-label">Category:</span>
          <span id={`${graphId}-info-category`} class="info-value category-value">Category</span>
        </div>

        <div id={`${graphId}-info-tags-container`} class="info-tags">
          <span class="info-label">Tags:</span>
          <div id={`${graphId}-info-tags`} class="tags-container"></div>
        </div>

        <!-- Enhanced content preview section -->
        <div id={`${graphId}-content-preview-container`} class="content-preview-container">
          <span class="info-label">Content Preview:</span>
          <div id={`${graphId}-content-preview`} class="content-preview">
            <div class="content-placeholder">
              <svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1" stroke-linecap="round" stroke-linejoin="round">
                <path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"></path>
                <polyline points="14 2 14 8 20 8"></polyline>
                <line x1="16" y1="13" x2="8" y2="13"></line>
                <line x1="16" y1="17" x2="8" y2="17"></line>
                <polyline points="10 9 9 9 8 9"></polyline>
              </svg>
              <p>Select a post to view content preview</p>
            </div>
          </div>
        </div>

        <div class="info-connections">
          <span class="info-label">Connections:</span>
          <ul id={`${graphId}-info-connections`} class="connections-list"></ul>
        </div>

        <a href="#" id={`${graphId}-info-link`} class="info-link" target="_blank" rel="noopener noreferrer">View Content</a>
      </div>
    </div>
  </div>
</div>

<!-- Minimal CSS -->
<style>
  /* Base styles for mini graph - 30% larger */
  .knowledge-graph-wrapper {
    width: 100%; /* Width will be set by style attribute */
    position: relative;
    margin-bottom: 1.5rem;
  }

  .graph-title {
    font-size: 1.1rem;
    margin-bottom: 0.75rem;
    color: var(--text-primary, #e2e8f0);
    font-weight: 600;
  }

  .mini-graph-container {
    width: 100%;
    aspect-ratio: 1 / 1; /* Create square aspect ratio */
    border-radius: 8px;
    overflow: hidden;
    border: 1px solid var(--card-border, rgba(56, 189, 248, 0.2));
    background: rgba(15, 23, 42, 0.2);
    min-height: 250px; /* Ensure minimum height */
  }

  /* Fullscreen toggle button */
  .fullscreen-toggle {
    position: absolute;
    top: 10px;
    right: 10px;
    width: 32px;
    height: 32px;
    border-radius: 6px;
    background: rgba(30, 41, 59, 0.7);
    border: 1px solid var(--border-primary, rgba(56, 189, 248, 0.3));
    color: var(--text-primary, #e2e8f0);
    display: flex;
    align-items: center;
    justify-content: center;
    cursor: pointer;
    z-index: 2;
    transition: all 0.2s ease;
    box-shadow: 0 2px 8px rgba(0, 0, 0, 0.2);
    opacity: 0; /* Hidden by default, shown on hover */
  }

  .knowledge-graph-wrapper:hover .fullscreen-toggle {
      opacity: 0.7; /* Show on hover */
  }

  .fullscreen-toggle:hover {
    opacity: 1;
    background: rgba(30, 41, 59, 0.9);
    transform: translateY(-2px);
  }

  .fullscreen-icon {
    width: 16px;
    height: 16px;
  }

  .hidden {
    display: none;
  }

  /* Fullscreen container */
  .fullscreen-container {
    display: none; /* Hidden by default */
    position: fixed;
    top: 0;
    left: 0;
    width: 100vw;
    height: 100vh;
    background: var(--bg-primary, #0f172a);
    z-index: 9999;
    flex-direction: column;
    overflow: hidden;
  }

  .fullscreen-container.active {
    display: flex;
  }

  .fullscreen-header {
    display: flex;
    justify-content: space-between;
    align-items: center;
    padding: 0.75rem 1.5rem;
    border-bottom: 1px solid var(--border-primary, rgba(56, 189, 248, 0.2));
    background: rgba(15, 23, 42, 0.8);
    backdrop-filter: blur(8px);
  }

  .fullscreen-title {
    font-size: 1.25rem;
    font-weight: 600;
    color: var(--text-primary, #e2e8f0);
  }

  .exit-fullscreen {
    width: 36px;
    height: 36px;
    border-radius: 6px;
    background: rgba(30, 41, 59, 0.7);
    border: 1px solid var(--border-primary, rgba(56, 189, 248, 0.2));
    color: var(--text-primary, #e2e8f0);
    display: flex;
    align-items: center;
    justify-content: center;
    cursor: pointer;
    transition: all 0.2s ease;
  }

  .exit-fullscreen:hover {
    background: rgba(30, 41, 59, 0.9);
  }

  .fullscreen-content {
    flex: 1;
    display: flex;
    overflow: hidden;
  }

  .fullscreen-graph {
    flex: 1;
    height: 100%;
    border-right: 1px solid var(--border-primary, rgba(56, 189, 248, 0.2));
  }

  /* Info panel */
  .info-panel {
    width: 0; /* Hidden by default */
    height: 100%;
    background: var(--bg-secondary, #1e293b);
    transition: width 0.3s ease;
    overflow: hidden;
    display: flex;
    flex-direction: column;
  }

  .info-panel.active {
    width: 45%; /* Percentage-based width for better responsiveness */
    min-width: 400px; /* Minimum width to ensure content is readable */
    max-width: 500px; /* Maximum width to prevent overflow on large screens */
  }

  .info-panel-header {
    display: flex;
    justify-content: space-between;
    align-items: center;
    padding: 1rem 1.5rem;
    border-bottom: 1px solid var(--border-primary, rgba(56, 189, 248, 0.1));
  }

  .info-title {
    font-size: 1.2rem;
    margin: 0;
    color: var(--text-primary, #e2e8f0);
    font-weight: 600;
  }

  .close-info {
    background: none;
    border: none;
    color: var(--text-secondary, #94a3b8);
    cursor: pointer;
    padding: 5px;
    border-radius: 50%;
    display: flex;
    align-items: center;
    justify-content: center;
    transition: all 0.2s ease;
  }

  .close-info:hover {
    color: var(--text-primary, #e2e8f0);
    background: rgba(255, 255, 255, 0.1);
  }

  .info-content {
    padding: 1.5rem;
    overflow-y: auto;
    flex: 1;
  }

  .info-type, .info-category, .info-tags, .info-connections, .content-preview-container {
    margin-bottom: 1.25rem;
  }

  .info-label {
    display: block;
    color: var(--text-secondary, #94a3b8);
    font-size: 0.85rem;
    margin-bottom: 0.5rem;
  }

  .type-value {
    display: inline-block;
    padding: 0.25rem 0.75rem;
    border-radius: 20px;
    font-size: 0.85rem;
    font-weight: 500;
  }

  .post-type {
    background-color: rgba(59, 130, 246, 0.15);
    color: #3B82F6;
  }

  .tag-type {
    background-color: rgba(16, 185, 129, 0.15);
    color: #10B981;
  }

  .category-value {
    display: inline-block;
    padding: 0.25rem 0.75rem;
    border-radius: 20px;
    font-size: 0.85rem;
  }

  .tags-container {
    display: flex;
    flex-wrap: wrap;
    gap: 0.5rem;
  }

  .tag {
    background: rgba(16, 185, 129, 0.1);
    color: #10B981;
    padding: 0.25rem 0.5rem;
    border-radius: 4px;
    font-size: 0.75rem;
    cursor: pointer;
    transition: all 0.2s ease;
  }

  .tag:hover {
    background: rgba(16, 185, 129, 0.2);
    transform: translateY(-2px);
  }

  .connections-list {
    padding-left: 0;
    list-style: none;
    margin: 0;
    max-height: 150px;
    overflow-y: auto;
  }

  .connections-list li {
    color: var(--text-secondary, #94a3b8);
    font-size: 0.9rem;
    margin-bottom: 0.5rem;
  }

  .connections-list a {
    color: var(--accent-primary, #38bdf8);
    text-decoration: none;
    transition: color 0.2s ease;
    cursor: pointer;
  }

  .connections-list a:hover {
    color: var(--accent-secondary, #06b6d4);
    text-decoration: underline;
  }

  /* Content Preview Styles - Enhanced */
  .content-preview-container {
    border-top: 1px solid var(--border-primary, rgba(56, 189, 248, 0.1));
    padding-top: 1.25rem;
    margin-top: 1.25rem;
  }

  .content-preview {
    max-height: 350px; /* Taller content area */
    overflow-y: auto;
    background: rgba(15, 23, 42, 0.3);
    border-radius: 6px;
    border: 1px solid var(--border-primary, rgba(56, 189, 248, 0.1));
    padding: 1rem;
    font-size: 0.9rem;
    line-height: 1.5;
    color: var(--text-primary, #e2e8f0);
  }

  .content-preview::-webkit-scrollbar {
    width: 6px;
  }

  .content-preview::-webkit-scrollbar-track {
    background: rgba(15, 23, 42, 0.2);
    border-radius: 3px;
  }

  .content-preview::-webkit-scrollbar-thumb {
    background: rgba(56, 189, 248, 0.2);
    border-radius: 3px;
  }

  .content-preview::-webkit-scrollbar-thumb:hover {
    background: rgba(56, 189, 248, 0.3);
  }

  .content-placeholder {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    padding: 2rem 1rem;
    color: var(--text-tertiary, #64748b);
    text-align: center;
  }

  .content-placeholder svg {
    margin-bottom: 1rem;
    opacity: 0.5;
  }

  .content-placeholder p {
    margin: 0;
    font-size: 0.9rem;
  }

  .placeholder-note {
    margin-top: 0.5rem;
    font-size: 0.8rem;
    opacity: 0.8;
  }

  /* Styling for post content preview */
  .post-content-preview {
    font-family: var(--font-sans, system-ui, -apple-system, BlinkMacSystemFont, sans-serif);
  }

  .post-content-preview h1,
  .post-content-preview h2,
  .post-content-preview h3,
  .post-content-preview h4,
  .post-content-preview h5,
  .post-content-preview h6 {
    color: var(--text-primary, #e2e8f0);
    margin-top: 1rem;
    margin-bottom: 0.5rem;
    font-weight: 600;
    line-height: 1.3;
  }

  .post-content-preview h1 { font-size: 1.4rem; }
  .post-content-preview h2 { font-size: 1.25rem; }
  .post-content-preview h3 { font-size: 1.1rem; }
  .post-content-preview h4,
  .post-content-preview h5,
  .post-content-preview h6 { font-size: 1rem; }

  .post-content-preview p {
    margin-bottom: 1rem;
  }

  .post-content-preview a {
    color: var(--accent-primary, #38bdf8);
    text-decoration: none;
    border-bottom: 1px dotted var(--accent-primary, #38bdf8);
    transition: all 0.2s ease;
  }

  .post-content-preview a:hover {
    color: var(--accent-secondary, #06b6d4);
    border-bottom-style: solid;
  }

  .post-content-preview img {
    max-width: 100%;
    height: auto;
    border-radius: 4px;
    margin: 1rem 0;
  }

  .post-content-preview code {
    font-family: var(--font-mono, monospace);
    background: rgba(15, 23, 42, 0.5);
    padding: 0.2em 0.4em;
    border-radius: 3px;
    font-size: 0.85em;
  }

  .post-content-preview pre {
    background: rgba(15, 23, 42, 0.5);
    padding: 1rem;
    border-radius: 6px;
    overflow-x: auto;
    margin: 1rem 0;
    border: 1px solid var(--border-primary, rgba(56, 189, 248, 0.1));
  }

  .post-content-preview pre code {
    background: transparent;
    padding: 0;
    font-size: 0.85em;
    color: var(--text-primary, #e2e8f0);
  }

  .post-content-preview blockquote {
    border-left: 3px solid var(--accent-primary, #38bdf8);
    padding-left: 1rem;
    margin-left: 0;
    color: var(--text-secondary, #94a3b8);
    font-style: italic;
  }

  .post-content-preview ul,
  .post-content-preview ol {
    padding-left: 1.5rem;
    margin-bottom: 1rem;
  }

  .post-content-preview li {
    margin-bottom: 0.25rem;
  }

  /* Tag Posts Styling */
  .tag-posts-title {
    margin-top: 0;
    margin-bottom: 1rem;
    font-size: 1rem;
    color: var(--text-primary, #e2e8f0);
  }

  /* Tag List Styles for tag nodes */
  .related-posts-list {
    list-style: none;
    padding: 0;
    margin: 0.5rem 0 0 0;
  }

  .related-posts-list li {
    margin-bottom: 0.75rem;
    padding: 0.5rem;
    border-radius: 4px;
    background: rgba(15, 23, 42, 0.3);
    transition: all 0.2s ease;
  }

  .related-posts-list li:hover {
    background: rgba(15, 23, 42, 0.5);
  }

  .related-posts-list a {
    color: var(--accent-primary, #38bdf8);
    text-decoration: none;
    display: block;
    font-size: 0.85rem;
  }

  .related-posts-list a:hover {
    text-decoration: underline;
  }

  .related-posts-category {
    display: inline-block;
    font-size: 0.7rem;
    padding: 0.15rem 0.5rem;
    border-radius: 10px;
    margin-top: 0.25rem;
  }

  .info-link {
    display: block;
    background: linear-gradient(90deg, var(--accent-primary, #38bdf8), var(--accent-secondary, #06b6d4));
    color: var(--bg-primary, #0f172a);
    font-weight: 500;
    padding: 0.6rem 1.25rem;
    border-radius: 6px;
    text-decoration: none;
    text-align: center;
    transition: all 0.3s ease;
    box-shadow: 0 4px 10px rgba(6, 182, 212, 0.2);
    margin-top: 1rem;
  }

  .info-link:hover {
    transform: translateY(-2px);
    box-shadow: 0 6px 15px rgba(6, 182, 212, 0.3);
  }

  /* Media queries for mobile responsiveness */
  @media screen and (max-width: 768px) {
    .fullscreen-content {
      flex-direction: column;
    }

    .fullscreen-graph {
      height: 60%;
      border-right: none;
      border-bottom: 1px solid var(--border-primary, rgba(56, 189, 248, 0.2));
    }

    .info-panel {
      width: 100%;
      height: 0; /* Start hidden */
    }

    .info-panel.active {
      width: 100%;
      height: 40%; /* Take remaining space */
    }

    .content-preview {
      max-height: 150px; /* Smaller height on mobile */
    }
  }
</style>

<!-- Standalone initialization script -->
<script define:vars={{ graphId, graphData, predefinedColors }}>
  // Wait for page to fully load
  window.addEventListener('load', function() {
    // Retry initialization multiple times in case Cytoscape or the DOM isn't ready yet
    let retries = 0;
    const maxRetries = 5;
    const retryInterval = 500; // ms

    function initGraph() {
      // Ensure Cytoscape is loaded
      if (typeof cytoscape === 'undefined') {
        console.warn(`[MiniGraph] Cytoscape not loaded, retry ${retries+1}/${maxRetries}...`);
        if (retries < maxRetries) {
          retries++;
          setTimeout(initGraph, retryInterval);
        } else {
          console.error("[MiniGraph] Cytoscape library not available after multiple attempts.");
          const container = document.getElementById(graphId);
          if (container) {
            container.innerHTML = '<div style="padding:10px;color:#a0aec0;text-align:center;">Graph library not loaded</div>';
          }
        }
        return;
      }

      // Verify container exists
      const container = document.getElementById(graphId);
      if (!container) {
        console.error(`[MiniGraph] Container #${graphId} not found.`);
        return;
      }

      // Fullscreen elements
      const fullscreenToggle = document.getElementById(`${graphId}-fullscreen-toggle`);
      const fullscreenContainer = document.getElementById(`${graphId}-fullscreen`);
      const fullscreenGraph = document.getElementById(`${graphId}-fullscreen-graph`);
      const exitFullscreenBtn = document.getElementById(`${graphId}-exit-fullscreen`);
      const fullscreenEnterIcon = document.getElementById(`${graphId}-fullscreen-enter`);
      const fullscreenExitIcon = document.getElementById(`${graphId}-fullscreen-exit`);
      const infoPanel = document.getElementById(`${graphId}-info-panel`);
      const closeInfoBtn = document.getElementById(`${graphId}-close-info`);

      // State variables
      let isFullscreen = false;
      let cy = null;         // Mini graph instance
      let cyFullscreen = null; // Fullscreen graph instance
      let originalStyles = {}; // To restore page layout after exiting fullscreen
      let selectedNode = null; // Currently selected node for fullscreen interactions

      try {
        // Check if we have any nodes
        if (graphData.nodes.length === 0) {
          container.innerHTML = '<div style="padding:10px;color:#a0aec0;text-align:center;">No connections</div>';
          return;
        }

        // Generate node colors based on type and category
        const nodeElements = graphData.nodes.map(node => {
          let nodeColor = '#3B82F6'; // Default blue for posts

          if (node.type === 'tag') {
            nodeColor = '#10B981'; // Green for tags
          } else if (node.type === 'post' && node.category) {
            // Use category color for posts if available
            nodeColor = predefinedColors[node.category] || '#3B82F6';
          }

          // Adjust opacity based on level
          const levelOpacity = node.level === 0 ? 1 :
                               node.level === 1 ? 0.85 : 0.7;

          // Calculate node size based on level
          const nodeSize = node.level === 0 ? 30 :
                           node.level === 1 ? 22 : 16;

          return {
            data: {
              id: node.id,
              label: node.label,
              type: node.type,
              level: node.level,
              category: node.category,
              tags: node.tags || [],
              content: node.content || '', // Include content for post nodes
              color: nodeColor,
              opacity: levelOpacity,
              size: nodeSize,
              url: node.url || '#'
            }
          };
        });

        // Generate edge elements
        const edgeElements = graphData.edges.map((edge, index) => ({
          data: {
            id: `e${index}`,
            source: edge.source,
            target: edge.target,
            type: edge.type
          }
        }));

        // Combine nodes and edges
        const elements = [...nodeElements, ...edgeElements];

        // Define common style array to be used by both graph instances
        const graphStyles = [
          // Base node style
          {
            selector: 'node',
            style: {
              'background-color': 'data(color)',
              'label': 'data(label)',
              'width': 'data(size)',
              'height': 'data(size)',
              'font-size': '8px',
              'color': '#E2E8F0',
              'text-valign': 'bottom',
              'text-halign': 'center',
              'text-margin-y': 5,
              'text-wrap': 'ellipsis',
              'text-max-width': '60px',
              'text-background-opacity': 0.7,
              'text-background-color': '#0F1219',
              'text-background-padding': '2px',
              'text-background-shape': 'roundrectangle',
              'border-width': 2,
              'border-color': '#0F1219',
              'border-opacity': 0.8,
              'opacity': 'data(opacity)'
            }
          },
          // Post node styles
          {
            selector: 'node[type="post"]',
            style: {
              'shape': 'ellipse',
              'text-max-width': '80px'
            }
          },
          // Current post node (Level 0)
          {
            selector: 'node[type="post"][level=0]',
            style: {
              'border-width': 3,
              'border-color': '#38BDF8'
            }
          },
          // Tag node styles
          {
            selector: 'node[type="tag"]',
            style: {
              'shape': 'diamond',
              'width': 'mapData(level, 0, 2, 25, 15)',
              'height': 'mapData(level, 0, 2, 25, 15)'
            }
          },
          // Edge styles
          {
            selector: 'edge',
            style: {
              'width': 1.5,
              'line-color': 'rgba(226, 232, 240, 0.2)',
              'curve-style': 'bezier',
              'opacity': 0.6
            }
          },
          // Post-tag edges
          {
            selector: 'edge[type="post-tag"]',
            style: {
              'line-color': 'rgba(16, 185, 129, 0.7)',
              'line-style': 'solid'
            }
          },
          // Related post edges
          {
            selector: 'edge[type="post-related"]',
            style: {
              'line-color': 'rgba(59, 130, 246, 0.7)',
              'line-style': 'dashed'
            }
          },
          // Level 2 connections
          {
            selector: 'edge[type="tag-post"]',
            style: {
              'line-color': 'rgba(16, 185, 129, 0.4)',
              'line-style': 'dotted',
              'opacity': 0.5
            }
          },
          // Highlight styles
          {
            selector: '.highlighted',
            style: {
              'background-color': 'data(color)',
              'border-color': '#FFFFFF',
              'border-width': 3,
              'text-background-opacity': 0.9,
              'color': '#FFFFFF',
              'opacity': 1,
              'z-index': 20
            }
          },
          {
            selector: '.faded',
            style: {
              'opacity': 0.2,
              'text-opacity': 0.3
            }
          },
          {
            selector: 'node:selected',
            style: {
              'border-width': 4,
              'border-color': '#FFFFFF'
            }
          }
        ];

        // Initialize mini graph with cose layout
        cy = cytoscape({
          container,
          elements,
          style: graphStyles,
          layout: {
            name: 'cose',
            idealEdgeLength: 50,
            nodeOverlap: 20,
            refresh: 20,
            fit: true,
            padding: 15,
            randomize: false,
            componentSpacing: 70,
            nodeRepulsion: 400000,
            edgeElasticity: 100,
            nestingFactor: 5,
            gravity: 80,
            numIter: 1000,
            initialTemp: 200,
            coolingFactor: 0.95,
            minTemp: 1.0,
            animate: false
          },
          minZoom: 0.5,
          maxZoom: 2,
          wheelSensitivity: 0.2
        });

        // Fit graph to container
        cy.fit(undefined, 20);

        // Add event listeners
        cy.on('tap', 'node', function(evt) {
          const node = evt.target;
          highlightNode(node, cy);
          // Navigate on click in mini-graph
          const url = node.data('url');
          if (url && url !== '#') {
              window.location.href = url;
          }
        });

        // Add fullscreen toggle functionality if the button exists
        if (fullscreenToggle && fullscreenContainer) {
          fullscreenToggle.addEventListener('click', function() {
            enterFullscreenMode();
          });
        }

        // Exit fullscreen when button is clicked
        if (exitFullscreenBtn) {
          exitFullscreenBtn.addEventListener('click', function() {
            exitFullscreenMode();
          });
        }

        // Close info panel when close button is clicked
        if (closeInfoBtn && infoPanel) {
          closeInfoBtn.addEventListener('click', function() {
            infoPanel.classList.remove('active');
          });
        }

        // Toggle fullscreen mode by keyboard ESC
        document.addEventListener('keydown', function(e) {
          if (e.key === 'Escape' && isFullscreen) {
            exitFullscreenMode();
          }
        });

        // Function to enter fullscreen mode
        function enterFullscreenMode() {
          if (isFullscreen) return;

          isFullscreen = true;

          // Show fullscreen container
          fullscreenContainer.classList.add('active');

          // Update fullscreen toggle icons
          fullscreenEnterIcon.classList.add('hidden');
          fullscreenExitIcon.classList.remove('hidden');

          // Store original styles of important page elements to restore later
          saveOriginalStyles();

          // Initialize fullscreen graph if not already done
          if (!cyFullscreen && fullscreenGraph) {
            initFullscreenGraph();
          } else if (cyFullscreen) {
             // If already initialized, just resize and fit
             setTimeout(() => {
                 cyFullscreen.resize();
                 cyFullscreen.fit(undefined, 30);
                 cyFullscreen.center();
             }, 50); // Small delay for transition
          }

          // Prevent body scroll
          document.body.style.overflow = 'hidden';
        }

        // Function to exit fullscreen mode
        function exitFullscreenMode() {
          if (!isFullscreen) return;

          isFullscreen = false;

          // Hide fullscreen container
          fullscreenContainer.classList.remove('active');

          // Update fullscreen toggle icons
          fullscreenEnterIcon.classList.remove('hidden');
          fullscreenExitIcon.classList.add('hidden');

          // Close info panel
          if (infoPanel) {
            infoPanel.classList.remove('active');
          }

          // Restore original page layout
          restoreOriginalStyles();

          // Allow body scroll again
          document.body.style.overflow = '';

          // Resize mini graph after exit
          setTimeout(() => {
              if (cy) {
                  cy.resize();
                  cy.fit(undefined, 20);
              }
          }, 50);
        }

        // Save original styles of page elements before entering fullscreen
        function saveOriginalStyles() {
          originalStyles = {
            bodyOverflow: document.body.style.overflow,
            // Add other elements that need to be restored later
          };

          // Check for sidebar, content containers, etc. and save their styles
          const sidebar = document.querySelector('.post-sidebar'); // Use specific class
          const mainContent = document.querySelector('.post-main-column'); // Use specific class

          if (sidebar) {
            originalStyles.sidebar = {
              element: sidebar,
              display: sidebar.style.display,
              visibility: sidebar.style.visibility,
              position: sidebar.style.position,
              zIndex: sidebar.style.zIndex,
              width: sidebar.style.width, // Save width too
              flex: sidebar.style.flex,
              gridColumn: sidebar.style.gridColumn
            };
          }

          if (mainContent) {
            originalStyles.mainContent = {
              element: mainContent,
              marginLeft: mainContent.style.marginLeft,
              width: mainContent.style.width,
              maxWidth: mainContent.style.maxWidth
            };
          }
        }

        // Restore original styles when exiting fullscreen
        function restoreOriginalStyles() {
          document.body.style.overflow = originalStyles.bodyOverflow || '';

          // Restore sidebar if it exists
          if (originalStyles.sidebar && originalStyles.sidebar.element) {
            const sidebar = originalStyles.sidebar.element;
            Object.assign(sidebar.style, originalStyles.sidebar); // Restore all saved styles
            sidebar.style.removeProperty('position'); // Ensure position is reset if it was static
          }

          // Restore main content if it exists
          if (originalStyles.mainContent && originalStyles.mainContent.element) {
            const mainContent = originalStyles.mainContent.element;
             Object.assign(mainContent.style, originalStyles.mainContent);
          }

          // Force reflow to ensure styles apply correctly
          setTimeout(() => {
              const contentArea = document.querySelector('.post-content');
              if (contentArea) {
                  const display = contentArea.style.display;
                  contentArea.style.display = 'none';
                  void contentArea.offsetHeight; // Trigger reflow
                  contentArea.style.display = display;
              }
          }, 0);
        }

        // Initialize the fullscreen graph
        function initFullscreenGraph() {
          if (!fullscreenGraph) return;

          // Create a new cytoscape instance for fullscreen with the same data
          cyFullscreen = cytoscape({
            container: fullscreenGraph,
            elements: elements,
            style: graphStyles,
            layout: {
              name: 'cose',
              idealEdgeLength: 80,
              nodeOverlap: 20,
              refresh: 20,
              fit: true,
              padding: 30,
              randomize: false,
              componentSpacing: 100,
              nodeRepulsion: 450000,
              edgeElasticity: 100,
              nestingFactor: 5,
              gravity: 80,
              numIter: 1000,
              initialTemp: 200,
              coolingFactor: 0.95,
              minTemp: 1.0,
              animate: false
            },
            minZoom: 0.2,
            maxZoom: 3,
            wheelSensitivity: 0.3
          });

          // Fit graph to container
          cyFullscreen.fit(undefined, 30);

          // Add fullscreen interactions
          addFullscreenInteractions();
        }

        // Add interactions for fullscreen mode
        function addFullscreenInteractions() {
          // Click on node to show info
          cyFullscreen.on('tap', 'node', function(evt) {
            const node = evt.target;
            selectedNode = node; // Store the selected node
            const nodeData = node.data();

            // Highlight the selected node
            highlightNode(node, cyFullscreen);

            // Show info panel
            showNodeInfo(nodeData);
          });

          // Click on background to reset
          cyFullscreen.on('tap', function(evt) {
            if (evt.target === cyFullscreen) {
              // Remove highlights
              cyFullscreen.elements().removeClass('highlighted faded');
              selectedNode = null;

              // Hide info panel
              if (infoPanel) {
                infoPanel.classList.remove('active');
              }
            }
          });
        }

        // Highlight a node and its connections
        function highlightNode(node, cyInstance) {
          // Remove previous highlights
          cyInstance.elements().removeClass('highlighted faded');

          // Highlight selected node and its neighborhood
          node.addClass('highlighted');
          node.neighborhood().addClass('highlighted');

          // Fade the rest
          cyInstance.elements().difference(node.neighborhood().union(node)).addClass('faded');
        }

        // Enhanced showNodeInfo function to properly display content previews
        function showNodeInfo(nodeData) {
          if (!infoPanel) return;

          // Get elements inside the panel
          const infoTitleEl = document.getElementById(`${graphId}-info-title`);
          const infoTypeEl = document.getElementById(`${graphId}-info-type`);
          const categoryContainerEl = document.getElementById(`${graphId}-info-category-container`);
          const categoryEl = document.getElementById(`${graphId}-info-category`);
          const tagsContainerEl = document.getElementById(`${graphId}-info-tags-container`);
          const tagsEl = document.getElementById(`${graphId}-info-tags`);
          const contentPreviewContainerEl = document.getElementById(`${graphId}-content-preview-container`);
          const contentPreviewEl = document.getElementById(`${graphId}-content-preview`);
          const connectionsEl = document.getElementById(`${graphId}-info-connections`);
          const linkEl = document.getElementById(`${graphId}-info-link`);

          // Set node title
          if (infoTitleEl) infoTitleEl.textContent = nodeData.label;

          // Set node type
          if (infoTypeEl) {
              infoTypeEl.textContent = nodeData.type.charAt(0).toUpperCase() + nodeData.type.slice(1);
              infoTypeEl.className = `info-value type-value ${nodeData.type}-type`;
          }

          // Set category if it's a post
          if (categoryContainerEl && categoryEl) {
              if (nodeData.type === 'post' && nodeData.category) {
                categoryContainerEl.style.display = 'block';
                categoryEl.textContent = nodeData.category;
                const catColor = predefinedColors[nodeData.category] || '#A0AEC0';
                categoryEl.style.backgroundColor = `${catColor}33`;
                categoryEl.style.color = catColor;
              } else {
                categoryContainerEl.style.display = 'none';
              }
          }

          // Set tags
          if (tagsContainerEl && tagsEl) {
              if (nodeData.tags && nodeData.tags.length > 0) {
                tagsContainerEl.style.display = 'block';
                tagsEl.innerHTML = '';
                nodeData.tags.forEach(tag => {
                  const tagEl = document.createElement('span');
                  tagEl.className = 'tag';
                  tagEl.textContent = tag;
                  tagEl.addEventListener('click', () => {
                    const tagNode = cyFullscreen.getElementById(`tag-${tag}`);
                    if (tagNode.length > 0) {
                      selectedNode = tagNode; // Update selected node
                      highlightNode(tagNode, cyFullscreen);
                      showNodeInfo(tagNode.data());
                    }
                  });
                  tagsEl.appendChild(tagEl);
                });
              } else {
                tagsContainerEl.style.display = 'none';
              }
          }

          // Show or hide content preview based on node type
          if (contentPreviewContainerEl && contentPreviewEl) {
            // First, clear any previous content
            contentPreviewEl.innerHTML = '';

            if (nodeData.type === 'post') {
              // Always show content container for post nodes, even if content is missing
              contentPreviewContainerEl.style.display = 'block';

              if (nodeData.content && nodeData.content.trim() !== '') {
                // We have content to display
                contentPreviewEl.innerHTML = `
                  <div class="post-content-preview">
                    ${nodeData.content}
                  </div>
                `;
              } else {
                // No content available, show placeholder
                contentPreviewEl.innerHTML = `
                  <div class="content-placeholder">
                    <svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1" stroke-linecap="round" stroke-linejoin="round">
                      <path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"></path>
                      <polyline points="14 2 14 8 20 8"></polyline>
                      <line x1="16" y1="13" x2="8" y2="13"></line>
                      <line x1="16" y1="17" x2="8" y2="17"></line>
                      <polyline points="10 9 9 9 8 9"></polyline>
                    </svg>
                    <p>No content preview available</p>
                    <p class="placeholder-note">Click the link below to read the full post</p>
                  </div>
                `;
              }
            } else if (nodeData.type === 'tag') {
              // For tag nodes, show related posts instead
              contentPreviewContainerEl.style.display = 'block';

              // Find posts that are connected to this tag
              const tagId = nodeData.id;
              const currentNode = cyFullscreen.getElementById(tagId);
              const connectedPosts = currentNode.neighborhood('node[type="post"]');

              if (connectedPosts.length > 0) {
                contentPreviewEl.innerHTML = `
                  <div class="tag-related-posts">
                    <h4 class="tag-posts-title">Posts with tag "${nodeData.label}":</h4>
                    <ul class="related-posts-list">
                      ${Array.from(connectedPosts).map(post => {
                        const postData = post.data();
                        const categoryColor = postData.category && predefinedColors[postData.category]
                          ? predefinedColors[postData.category]
                          : '#A0AEC0';

                        return `
                          <li>
                            <a href="#" class="related-post-link" data-node-id="${postData.id}">
                              ${postData.label}
                            </a>
                            ${postData.category ? `
                              <span class="related-posts-category" style="background-color:${categoryColor}33;color:${categoryColor}">
                                ${postData.category}
                              </span>
                            ` : ''}
                          </li>
                        `;
                      }).join('')}
                    </ul>
                  </div>
                `;

                // Add click handlers for related posts
                setTimeout(() => {
                  const relatedPostLinks = contentPreviewEl.querySelectorAll('.related-post-link');
                  relatedPostLinks.forEach(link => {
                    link.addEventListener('click', (e) => {
                      e.preventDefault();
                      const nodeId = link.getAttribute('data-node-id');
                      const postNode = cyFullscreen.getElementById(nodeId);
                      if (postNode.length > 0) {
                        selectedNode = postNode;
                        highlightNode(postNode, cyFullscreen);
                        showNodeInfo(postNode.data());
                      }
                    });
                  });
                }, 10); // Small delay to ensure elements are rendered
              } else {
                contentPreviewEl.innerHTML = `
                  <div class="content-placeholder">
                    <p>No posts connected to tag "${nodeData.label}" in the current view.</p>
                  </div>
                `;
              }
            } else {
              // Hide for other node types
              contentPreviewContainerEl.style.display = 'none';
            }
          }

          // Set connections - now with interactive links
          if (connectionsEl) {
              connectionsEl.innerHTML = '';
              const currentNode = cyFullscreen.getElementById(nodeData.id); // Get current node from fullscreen instance
              const neighbors = currentNode.neighborhood('node');

              if (neighbors.length > 0) {
                neighbors.forEach(neighbor => {
                  const neighborData = neighbor.data();
                  const li = document.createElement('li');
                  const a = document.createElement('a');
                  a.href = '#';
                  a.textContent = neighborData.label;
                  a.addEventListener('click', (e) => {
                    e.preventDefault();
                    selectedNode = neighbor; // Update selected node
                    highlightNode(neighbor, cyFullscreen);
                    showNodeInfo(neighborData);
                  });
                  li.appendChild(a);
                  connectionsEl.appendChild(li);
                });
              } else {
                const li = document.createElement('li');
                li.textContent = 'No connections';
                connectionsEl.appendChild(li);
              }
          }

          // Set link based on node type - dynamic text and URL
          if (linkEl) {
              if (nodeData.type === 'post') {
                linkEl.textContent = 'Read Post';
                linkEl.href = nodeData.url || `/posts/${nodeData.id}/`;
              } else if (nodeData.type === 'tag') {
                linkEl.textContent = 'View Tag';
                linkEl.href = nodeData.url || `/tag/${nodeData.label}/`;
              } else {
                linkEl.textContent = 'View Content';
                linkEl.href = nodeData.url || '#';
              }
          }

          // Show the panel
          infoPanel.classList.add('active');
        }

      } catch (error) {
        console.error('[MiniGraph] Error initializing graph:', error);
        container.innerHTML = '<div style="padding:10px;color:#a0aec0;text-align:center;">Error loading graph</div>';
      }
    }

    // Start initialization attempt
    initGraph();
  });
</script>