/**
 * knowledge-explorer.mjs — Knowledge Graph explorer component
 * Search entities, view relations, browse type breakdowns with glassmorphic UI.
 * Graph view: D3.js force-directed visualization with zoom, pan, and drag.
 */

import store from '../store.mjs';
import api from '../api.mjs';
import { $, $$, createElement, icon, escapeHtml, empty, debounce } from '../utils/dom.mjs';
import { truncate } from '../utils/format.mjs';

// ── Constants ──

const TYPE_STYLES = {
  person:     { color: 'var(--accent-primary)',      label: 'Person' },
  concept:    { color: 'var(--info)',                 label: 'Concept' },
  technology: { color: 'var(--success)',              label: 'Technology' },
  location:   { color: 'var(--warning)',              label: 'Location' },
  other:      { color: 'var(--text-tertiary)',        label: 'Other' }
};

// Concrete hex colors for D3 (CSS vars not resolved in SVG attributes)
const TYPE_COLORS = {
  person:     '#6366f1',
  concept:    '#a855f7',
  technology: '#22c55e',
  location:   '#f97316',
  other:      '#6b7280'
};

// ── DOM references ──

let container = null;
let statsHeaderEl = null;
let searchInput = null;
let searchRowEl = null;
let resultsEl = null;
let detailEl = null;
let statsBreakdownEl = null;
let emptyStateEl = null;
let loadingEl = null;
let graphContainerEl = null;
let viewToggleBtn = null;
let selectedEntityId = null;

// ── View state ──

let currentView = 'list'; // 'list' | 'graph'
let d3Simulation = null;

// ──────────────────────────────────────
// Public init
// ──────────────────────────────────────

export function init() {
  container = $('#view-knowledge');
  if (!container) {
    console.warn('[knowledge-explorer] #view-knowledge not found');
    return;
  }

  buildDOM();
  subscribeToStore();
  loadStats();
}

// ──────────────────────────────────────
// DOM construction
// ──────────────────────────────────────

function buildDOM() {
  empty(container);
  container.classList.add('knowledge-explorer');

  // Header
  const header = createElement('div', {
    style: { display: 'flex', alignItems: 'center', justifyContent: 'space-between', flexWrap: 'wrap', gap: 'var(--space-sm)' }
  });

  const titleRow = createElement('div', {
    style: { display: 'flex', alignItems: 'center', gap: 'var(--space-sm)' }
  }, [
    icon('database', { size: 24 }),
    createElement('h2', {
      style: { margin: '0', fontSize: 'var(--font-size-xl)', color: 'var(--text-primary)' }
    }, ['Knowledge Graph'])
  ]);

  const headerRight = createElement('div', {
    style: { display: 'flex', gap: 'var(--space-sm)', alignItems: 'center', flexWrap: 'wrap' }
  });

  statsHeaderEl = createElement('div', {
    style: { display: 'flex', gap: 'var(--space-sm)', alignItems: 'center' }
  });

  viewToggleBtn = createElement('button', { className: 'kg-view-toggle' });
  updateToggleBtn();
  viewToggleBtn.addEventListener('click', toggleView);

  const exportBtn = createElement('button', { className: 'kg-view-toggle', title: 'Export knowledge graph as JSON' });
  exportBtn.append(icon('download', { size: 14 }), ' Export');
  exportBtn.addEventListener('click', handleExport);

  // Hidden file input for import
  const importInput = createElement('input', { type: 'file', accept: '.json', style: { display: 'none' } });
  importInput.addEventListener('change', (e) => handleImport(e.target.files[0], importInput));
  container.appendChild(importInput);

  const importBtn = createElement('button', { className: 'kg-view-toggle', title: 'Import knowledge graph from JSON' });
  importBtn.append(icon('upload', { size: 14 }), ' Import');
  importBtn.addEventListener('click', () => importInput.click());

  headerRight.appendChild(statsHeaderEl);
  headerRight.appendChild(exportBtn);
  headerRight.appendChild(importBtn);
  headerRight.appendChild(viewToggleBtn);
  header.appendChild(titleRow);
  header.appendChild(headerRight);
  container.appendChild(header);

  // Search bar
  searchRowEl = createElement('div', {
    style: { position: 'relative', display: 'flex', alignItems: 'center' }
  });

  const searchIconEl = createElement('span', {
    style: {
      position: 'absolute', left: '12px', top: '50%', transform: 'translateY(-50%)',
      color: 'var(--text-tertiary)', pointerEvents: 'none', display: 'flex'
    }
  }, [icon('search', { size: 16 })]);

  searchInput = createElement('input', {
    className: 'glass-input',
    type: 'text',
    placeholder: 'Search entities...',
    'aria-label': 'Search knowledge graph',
    style: { width: '100%', paddingLeft: '40px' }
  });

  searchRowEl.appendChild(searchIconEl);
  searchRowEl.appendChild(searchInput);
  container.appendChild(searchRowEl);

  // Loading state — 3 skeleton cards
  loadingEl = createElement('div', {
    style: { display: 'none', flexDirection: 'column', gap: 'var(--space-sm)' }
  });
  for (let i = 0; i < 3; i++) {
    loadingEl.appendChild(createElement('div', { className: 'skeleton skeleton-card' }));
  }
  container.appendChild(loadingEl);

  // Empty state
  emptyStateEl = createElement('div', {
    className: 'glass-subtle animate-fade-in',
    style: {
      display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center',
      padding: 'var(--space-xl)', gap: 'var(--space-md)', textAlign: 'center',
      borderRadius: 'var(--radius-lg)', minHeight: '200px'
    }
  }, [
    icon('database', { size: 48, className: '' }),
    createElement('p', {
      style: { color: 'var(--text-secondary)', margin: '0', fontSize: 'var(--font-size-base)' }
    }, ['Search the knowledge graph']),
    createElement('p', {
      style: { color: 'var(--text-tertiary)', margin: '0', fontSize: 'var(--font-size-sm)' }
    }, ['Type a query above to find entities and their relationships'])
  ]);
  emptyStateEl.querySelector('svg').style.color = 'var(--text-tertiary)';
  container.appendChild(emptyStateEl);

  // Results list
  resultsEl = createElement('div', {
    style: { display: 'none', flexDirection: 'column', gap: 'var(--space-sm)' }
  });
  container.appendChild(resultsEl);

  // Graph container (hidden by default)
  graphContainerEl = createElement('div', { className: 'kg-graph-container' });
  container.appendChild(graphContainerEl);

  // Entity detail panel (shared between list and graph views)
  detailEl = createElement('div', {
    style: { display: 'none', flexDirection: 'column', gap: 'var(--space-sm)' }
  });
  container.appendChild(detailEl);

  // Stats breakdown (bottom)
  statsBreakdownEl = createElement('div', {
    style: { display: 'none', flexDirection: 'column', gap: 'var(--space-sm)', marginTop: 'auto' }
  });
  container.appendChild(statsBreakdownEl);

  // Wire up search input with debounce
  const debouncedSearch = debounce(handleSearch, 300);
  searchInput.addEventListener('input', debouncedSearch);
}

// ──────────────────────────────────────
// View toggle
// ──────────────────────────────────────

function updateToggleBtn() {
  if (!viewToggleBtn) return;
  empty(viewToggleBtn);
  if (currentView === 'graph') {
    viewToggleBtn.append(icon('list', { size: 14 }), ' List View');
  } else {
    viewToggleBtn.append(icon('share-2', { size: 14 }), ' Graph View');
  }
}

async function toggleView() {
  if (currentView === 'list') {
    currentView = 'graph';
    updateToggleBtn();
    // Hide list elements
    searchRowEl.style.display = 'none';
    loadingEl.style.display = 'none';
    emptyStateEl.style.display = 'none';
    resultsEl.style.display = 'none';
    hideDetail();
    // Show graph
    graphContainerEl.style.display = 'block';
    await loadAndRenderGraph();
  } else {
    currentView = 'list';
    updateToggleBtn();
    // Cleanup graph
    cleanupGraph();
    graphContainerEl.style.display = 'none';
    // Restore list
    searchRowEl.style.display = '';
    if (searchInput && searchInput.value.trim()) {
      handleSearch();
    } else {
      showEmptyState();
    }
  }
}

// ──────────────────────────────────────
// Store subscriptions
// ──────────────────────────────────────

function subscribeToStore() {
  store.subscribe(
    (s) => s.knowledge.stats,
    (stats) => renderStats(stats)
  );

  store.subscribe(
    (s) => s.knowledge.searchResults,
    (results) => renderResults(results)
  );
}

// ──────────────────────────────────────
// Data loading
// ──────────────────────────────────────

async function loadStats() {
  try {
    const stats = await api.getKGStats();
    store.dispatch('SET_KG_STATS', stats);
  } catch (err) {
    console.warn('[knowledge-explorer] Failed to load KG stats:', err.message);
  }
}

async function handleSearch() {
  const query = searchInput.value.trim();

  if (!query) {
    store.dispatch('SET_KG_RESULTS', []);
    hideDetail();
    showEmptyState();
    return;
  }

  showLoading();
  hideEmptyState();

  try {
    const results = await api.searchEntities(query);
    store.dispatch('SET_KG_RESULTS', Array.isArray(results) ? results : []);
  } catch (err) {
    console.error('[knowledge-explorer] Search error:', err);
    store.dispatch('SET_KG_RESULTS', []);
  } finally {
    hideLoading();
  }
}

async function loadEntityRelations(entityId) {
  try {
    const relations = await api.getEntityRelations(entityId);
    return Array.isArray(relations) ? relations : [];
  } catch (err) {
    console.error('[knowledge-explorer] Relations error:', err);
    return [];
  }
}

// ──────────────────────────────────────
// Graph — load and render
// ──────────────────────────────────────

async function loadAndRenderGraph() {
  empty(graphContainerEl);

  const loadingDiv = createElement('div', { className: 'kg-graph-loading' }, [
    'Loading knowledge graph\u2026'
  ]);
  graphContainerEl.appendChild(loadingDiv);

  try {
    const data = await api.getKGGraph(150);
    const { entities = [], relations = [] } = data;
    empty(graphContainerEl);
    renderGraph(entities, relations);
  } catch (err) {
    console.error('[knowledge-explorer] Graph load error:', err);
    empty(graphContainerEl);
    graphContainerEl.appendChild(
      createElement('div', { className: 'kg-graph-empty' }, ['Failed to load graph data.'])
    );
  }
}

function renderGraph(entities, relations) {
  const d3 = window.d3;
  if (!d3) {
    graphContainerEl.appendChild(
      createElement('div', { className: 'kg-graph-empty' }, [
        'D3.js is still loading \u2014 please try again in a moment.'
      ])
    );
    return;
  }

  if (entities.length === 0) {
    const emptyDiv = createElement('div', { className: 'kg-graph-empty' });
    const dbIcon = icon('database', { size: 48 });
    dbIcon.style.color = 'var(--text-tertiary)';
    const msg = createElement('p', { style: { margin: '0', maxWidth: '300px' } }, [
      'No entities yet. Chat with the agent or ingest content to build your knowledge graph.'
    ]);
    emptyDiv.appendChild(dbIcon);
    emptyDiv.appendChild(msg);
    graphContainerEl.appendChild(emptyDiv);
    return;
  }

  const width = graphContainerEl.clientWidth || 600;
  const height = Math.max(graphContainerEl.clientHeight || 500, 420);

  // Deep-copy nodes so D3 can mutate them
  const nodes = entities.map(e => ({ ...e }));
  const idToNode = new Map(nodes.map(n => [n.id, n]));

  const links = relations
    .filter(r => idToNode.has(r.source_id) && idToNode.has(r.target_id))
    .map(r => ({ source: r.source_id, target: r.target_id, label: r.relation || '' }));

  // ── SVG scaffold ──
  const svg = d3.create('svg')
    .attr('width', width)
    .attr('height', height)
    .style('cursor', 'grab')
    .style('background', 'transparent');

  // Arrowhead marker
  svg.append('defs').append('marker')
    .attr('id', 'kg-arrow')
    .attr('viewBox', '0 -4 8 8')
    .attr('refX', 24)
    .attr('refY', 0)
    .attr('markerWidth', 6)
    .attr('markerHeight', 6)
    .attr('orient', 'auto')
    .append('path')
    .attr('d', 'M0,-4L8,0L0,4')
    .attr('fill', 'rgba(255,255,255,0.2)');

  // Zoom/pan group
  const g = svg.append('g');

  svg.call(
    d3.zoom()
      .scaleExtent([0.15, 5])
      .on('zoom', (event) => {
        g.attr('transform', event.transform);
        svg.style('cursor', event.transform.k !== 1 ? 'grabbing' : 'grab');
      })
  );

  // ── Links ──
  const linkSel = g.append('g')
    .attr('stroke-opacity', 0.5)
    .selectAll('line')
    .data(links)
    .join('line')
    .attr('stroke', 'rgba(255,255,255,0.2)')
    .attr('stroke-width', 1.5)
    .attr('marker-end', 'url(#kg-arrow)');

  const linkLabelSel = g.append('g')
    .selectAll('text')
    .data(links)
    .join('text')
    .attr('text-anchor', 'middle')
    .attr('font-size', '9px')
    .attr('fill', 'rgba(255,255,255,0.35)')
    .attr('pointer-events', 'none')
    .text(d => d.label);

  // ── Nodes ──
  const nodeSel = g.append('g')
    .selectAll('g')
    .data(nodes)
    .join('g')
    .style('cursor', 'pointer')
    .call(
      d3.drag()
        .on('start', (event, d) => {
          if (!event.active) d3Simulation.alphaTarget(0.3).restart();
          d.fx = d.x; d.fy = d.y;
          svg.style('cursor', 'grabbing');
        })
        .on('drag', (event, d) => { d.fx = event.x; d.fy = event.y; })
        .on('end', (event, d) => {
          if (!event.active) d3Simulation.alphaTarget(0);
          d.fx = null; d.fy = null;
          svg.style('cursor', 'grab');
        })
    )
    .on('click', (event, d) => {
      event.stopPropagation();
      // Highlight selected node ring
      nodeSel.select('circle').attr('stroke-width', 2).attr('stroke-opacity', 0.6);
      d3.select(event.currentTarget).select('circle')
        .attr('stroke-width', 3.5)
        .attr('stroke-opacity', 1);
      selectEntity(d);
    });

  nodeSel.append('circle')
    .attr('r', 14)
    .attr('fill', d => TYPE_COLORS[(d.type || 'other').toLowerCase()] || TYPE_COLORS.other)
    .attr('fill-opacity', 0.85)
    .attr('stroke', 'rgba(255,255,255,0.4)')
    .attr('stroke-width', 2);

  nodeSel.append('text')
    .attr('dy', '28px')
    .attr('text-anchor', 'middle')
    .attr('font-size', '10px')
    .attr('fill', 'rgba(255,255,255,0.75)')
    .attr('pointer-events', 'none')
    .text(d => truncate(d.name || '', 18));

  // ── Force simulation ──
  d3Simulation = d3.forceSimulation(nodes)
    .force('link', d3.forceLink(links).id(d => d.id).distance(110))
    .force('charge', d3.forceManyBody().strength(-280))
    .force('center', d3.forceCenter(width / 2, height / 2))
    .force('collision', d3.forceCollide(22))
    .on('tick', () => {
      linkSel
        .attr('x1', d => d.source.x)
        .attr('y1', d => d.source.y)
        .attr('x2', d => d.target.x)
        .attr('y2', d => d.target.y);

      linkLabelSel
        .attr('x', d => (d.source.x + d.target.x) / 2)
        .attr('y', d => (d.source.y + d.target.y) / 2);

      nodeSel.attr('transform', d => `translate(${d.x},${d.y})`);
    });

  graphContainerEl.appendChild(svg.node());
}

function cleanupGraph() {
  if (d3Simulation) {
    d3Simulation.stop();
    d3Simulation = null;
  }
  empty(graphContainerEl);
}

// ──────────────────────────────────────
// Rendering — Stats
// ──────────────────────────────────────

function renderStats(stats) {
  if (!stats) return;

  empty(statsHeaderEl);

  const entities = stats.totalEntities || 0;
  const relations = stats.totalRelations || 0;

  statsHeaderEl.appendChild(
    createElement('span', { className: 'badge badge-accent' }, [`${entities} entities`])
  );
  statsHeaderEl.appendChild(
    createElement('span', { className: 'badge' }, [`${relations} relations`])
  );

  // Stats breakdown section
  renderStatsBreakdown(stats);
}

function renderStatsBreakdown(stats) {
  if (!stats || !stats.types) return;

  empty(statsBreakdownEl);
  statsBreakdownEl.style.display = 'flex';

  const divider = createElement('div', { className: 'divider' });
  statsBreakdownEl.appendChild(divider);

  const breakdownTitle = createElement('div', {
    style: {
      display: 'flex', alignItems: 'center', gap: 'var(--space-sm)',
      color: 'var(--text-secondary)', fontSize: 'var(--font-size-sm)', fontWeight: '500'
    }
  }, ['Entity Types']);
  statsBreakdownEl.appendChild(breakdownTitle);

  const typesGrid = createElement('div', {
    style: { display: 'flex', flexWrap: 'wrap', gap: 'var(--space-sm)' }
  });

  const types = stats.types || {};
  for (const [type, count] of Object.entries(types)) {
    const style = TYPE_STYLES[type] || TYPE_STYLES.other;
    const badge = createElement('span', {
      className: 'badge',
      style: { borderColor: style.color, color: style.color, borderWidth: '1px', borderStyle: 'solid' }
    }, [`${style.label}: ${count}`]);
    typesGrid.appendChild(badge);
  }

  if (Object.keys(types).length === 0) {
    typesGrid.appendChild(
      createElement('span', {
        style: { color: 'var(--text-tertiary)', fontSize: 'var(--font-size-sm)' }
      }, ['No entities yet'])
    );
  }

  statsBreakdownEl.appendChild(typesGrid);
}

// ──────────────────────────────────────
// Rendering — Search results
// ──────────────────────────────────────

function renderResults(results) {
  if (currentView !== 'list') return; // don't update list while in graph mode
  empty(resultsEl);
  hideDetail();

  if (!results || results.length === 0) {
    resultsEl.style.display = 'none';
    if (searchInput && searchInput.value.trim()) {
      // Query active but no results
      hideEmptyState();
      resultsEl.style.display = 'flex';
      resultsEl.appendChild(
        createElement('div', {
          className: 'glass-subtle',
          style: {
            padding: 'var(--space-lg)', textAlign: 'center',
            borderRadius: 'var(--radius-md)', color: 'var(--text-secondary)'
          }
        }, ['No entities found'])
      );
    } else {
      showEmptyState();
    }
    return;
  }

  hideEmptyState();
  resultsEl.style.display = 'flex';

  const countLabel = createElement('div', {
    style: { color: 'var(--text-tertiary)', fontSize: 'var(--font-size-sm)' }
  }, [`${results.length} result${results.length !== 1 ? 's' : ''}`]);
  resultsEl.appendChild(countLabel);

  for (const entity of results) {
    const card = buildEntityCard(entity);
    resultsEl.appendChild(card);
  }
}

function buildEntityCard(entity) {
  const typeKey = (entity.type || 'other').toLowerCase();
  const style = TYPE_STYLES[typeKey] || TYPE_STYLES.other;

  const typeBadge = createElement('span', {
    className: 'badge',
    style: {
      backgroundColor: style.color + '22',
      color: style.color,
      fontSize: 'var(--font-size-xs)',
      padding: '2px 8px',
      borderRadius: 'var(--radius-sm)'
    }
  }, [style.label]);

  const nameEl = createElement('div', {
    style: {
      display: 'flex', alignItems: 'center', gap: 'var(--space-sm)',
      fontWeight: '500', color: 'var(--text-primary)', fontSize: 'var(--font-size-base)'
    }
  }, [escapeHtml(entity.name || 'Unnamed'), typeBadge]);

  const summaryEl = createElement('div', {
    style: {
      color: 'var(--text-secondary)', fontSize: 'var(--font-size-sm)',
      lineHeight: '1.4'
    }
  }, [truncate(entity.summary || '', 150) || 'No summary available']);

  const card = createElement('div', {
    className: 'glass-card animate-fade-in',
    style: {
      padding: 'var(--space-md)', cursor: 'pointer',
      transition: 'var(--transition-fast)',
      borderRadius: 'var(--radius-md)'
    },
    dataset: { entityId: String(entity.id), entityType: typeKey }
  }, [nameEl, summaryEl]);

  card.addEventListener('mouseenter', () => {
    card.style.background = 'var(--glass-bg-hover)';
  });
  card.addEventListener('mouseleave', () => {
    card.style.background = '';
  });

  card.addEventListener('click', () => selectEntity(entity));

  return card;
}

// ──────────────────────────────────────
// Rendering — Entity detail + relations
// ──────────────────────────────────────

async function selectEntity(entity) {
  selectedEntityId = entity.id;

  // In list view: highlight the selected card
  if (currentView === 'list') {
    const cards = $$('.glass-card', resultsEl);
    for (const c of cards) {
      c.style.borderColor = c.dataset.entityId === String(entity.id)
        ? 'var(--accent-primary)' : '';
    }
  }

  // Show detail panel with loading state
  empty(detailEl);
  detailEl.style.display = 'flex';

  const detailHeader = createElement('div', {
    style: {
      display: 'flex', alignItems: 'center', gap: 'var(--space-sm)',
      color: 'var(--text-secondary)', fontSize: 'var(--font-size-sm)', fontWeight: '500'
    }
  }, [
    icon('expand', { size: 14 }),
    'Entity Detail'
  ]);
  detailEl.appendChild(detailHeader);

  const detailCard = createElement('div', {
    className: 'glass-card',
    style: { padding: 'var(--space-md)', borderRadius: 'var(--radius-md)' }
  });

  const typeKey = (entity.type || 'other').toLowerCase();
  const typeStyle = TYPE_STYLES[typeKey] || TYPE_STYLES.other;

  const detailTitle = createElement('div', {
    style: {
      display: 'flex', alignItems: 'center', gap: 'var(--space-sm)',
      fontWeight: '600', fontSize: 'var(--font-size-lg)', color: 'var(--text-primary)',
      marginBottom: 'var(--space-sm)'
    }
  }, [
    escapeHtml(entity.name || 'Unnamed'),
    createElement('span', {
      className: 'badge',
      style: {
        backgroundColor: typeStyle.color + '22', color: typeStyle.color,
        fontSize: 'var(--font-size-xs)', padding: '2px 8px'
      }
    }, [typeStyle.label])
  ]);

  const detailSummary = createElement('div', {
    style: {
      color: 'var(--text-secondary)', fontSize: 'var(--font-size-sm)',
      lineHeight: '1.5', marginBottom: 'var(--space-md)'
    }
  }, [entity.summary || 'No summary available']);

  detailCard.appendChild(detailTitle);
  detailCard.appendChild(detailSummary);

  // Relations loading indicator
  const relationsLoading = createElement('div', {
    className: 'shimmer',
    style: { height: '40px', borderRadius: 'var(--radius-sm)' }
  });
  detailCard.appendChild(relationsLoading);
  detailEl.appendChild(detailCard);

  // Fetch relations
  const relations = await loadEntityRelations(entity.id);

  // Check if still the selected entity (user may have clicked another)
  if (selectedEntityId !== entity.id) return;

  // Remove loading indicator
  if (relationsLoading.parentNode) relationsLoading.remove();

  if (relations.length === 0) {
    detailCard.appendChild(
      createElement('div', {
        style: { color: 'var(--text-tertiary)', fontSize: 'var(--font-size-sm)', fontStyle: 'italic' }
      }, ['No relations found'])
    );
    return;
  }

  const relationsHeader = createElement('div', {
    style: {
      color: 'var(--text-secondary)', fontSize: 'var(--font-size-sm)',
      fontWeight: '500', marginBottom: 'var(--space-xs)'
    }
  }, [`Relations (${relations.length})`]);
  detailCard.appendChild(relationsHeader);

  const relationsListEl = createElement('div', {
    style: { display: 'flex', flexDirection: 'column', gap: 'var(--space-xs)' }
  });

  for (const rel of relations) {
    const row = buildRelationRow(rel);
    relationsListEl.appendChild(row);
  }

  detailCard.appendChild(relationsListEl);
}

function buildRelationRow(rel) {
  const row = createElement('div', {
    className: 'glass-subtle',
    style: {
      display: 'flex', alignItems: 'center', gap: 'var(--space-xs)',
      padding: 'var(--space-sm)', borderRadius: 'var(--radius-sm)',
      fontSize: 'var(--font-size-sm)', flexWrap: 'wrap'
    }
  });

  const sourceLink = createElement('span', {
    style: { color: 'var(--accent-primary)', cursor: 'pointer', fontWeight: '500' }
  }, [escapeHtml(rel.sourceName || 'Unknown')]);
  sourceLink.addEventListener('click', (e) => {
    e.stopPropagation();
    searchForEntity(rel.sourceName);
  });

  const arrow = createElement('span', {
    style: { color: 'var(--text-tertiary)' }
  }, [' \u2192 ']);

  const relationLabel = createElement('span', {
    style: { color: 'var(--text-secondary)', fontStyle: 'italic' }
  }, [escapeHtml(rel.relation || 'related to')]);

  const targetArrow = createElement('span', {
    style: { color: 'var(--text-tertiary)' }
  }, [' \u2192 ']);

  const targetLink = createElement('span', {
    style: { color: 'var(--accent-primary)', cursor: 'pointer', fontWeight: '500' }
  }, [escapeHtml(rel.targetName || 'Unknown')]);
  targetLink.addEventListener('click', (e) => {
    e.stopPropagation();
    searchForEntity(rel.targetName);
  });

  row.appendChild(sourceLink);
  row.appendChild(arrow);
  row.appendChild(relationLabel);
  row.appendChild(targetArrow);
  row.appendChild(targetLink);

  return row;
}

function searchForEntity(name) {
  if (!name || !searchInput) return;
  // Switch back to list view first if in graph mode
  if (currentView === 'graph') toggleView();
  searchInput.value = name;
  searchInput.dispatchEvent(new Event('input'));
}

// ──────────────────────────────────────
// Visibility helpers
// ──────────────────────────────────────

function showEmptyState() {
  if (emptyStateEl) emptyStateEl.style.display = 'flex';
}

function hideEmptyState() {
  if (emptyStateEl) emptyStateEl.style.display = 'none';
}

function showLoading() {
  if (loadingEl) loadingEl.style.display = 'flex';
}

function hideLoading() {
  if (loadingEl) loadingEl.style.display = 'none';
}

function hideDetail() {
  if (detailEl) {
    detailEl.style.display = 'none';
    empty(detailEl);
  }
  selectedEntityId = null;
}

// ──────────────────────────────────────
// Export / Import
// ──────────────────────────────────────

async function handleExport() {
  try {
    const data = await api.exportKG();
    const json = JSON.stringify(data, null, 2);
    const blob = new Blob([json], { type: 'application/json' });
    const url = URL.createObjectURL(blob);
    const date = new Date().toISOString().slice(0, 10);
    const a = createElement('a', { href: url, download: `knowledge-graph-${date}.json` });
    document.body.appendChild(a);
    a.click();
    document.body.removeChild(a);
    URL.revokeObjectURL(url);
  } catch (err) {
    console.error('[knowledge-explorer] Export failed:', err);
  }
}

async function handleImport(file, inputEl) {
  if (!file) return;
  try {
    const text = await file.text();
    const data = JSON.parse(text);
    if (!data.entities || !data.relations) throw new Error('Invalid format: missing entities or relations');
    const result = await api.importKG(data);
    console.log('[knowledge-explorer] Import result:', result);
    await loadStats();
    if (searchInput && searchInput.value.trim()) handleSearch();
  } catch (err) {
    console.error('[knowledge-explorer] Import failed:', err);
  } finally {
    inputEl.value = '';
  }
}
