/* ============================================================
   graph.js — Knowledge Graph (캔버스 force 시뮬레이션, 의존성 없음)
   ============================================================ */
(function () {
  'use strict';

  const TYPE_COLOR = { protein: '#6699ff', pathway: '#a855f7', disease: '#f87171', axis: '#22d3ee', drug: '#34d399' };
  const TYPE_R = { protein: 4, pathway: 7, disease: 9, axis: 8, drug: 3 };

  let canvas, ctx, nodes = [], links = [], view = { x: 0, y: 0, k: 1 };
  let alpha = 1, running = false, raf = null;
  let drag = null, hover = null, pan = null, initialized = false, justDragged = false;
  let showDrug = false, focusDisease = '';

  function el(id) { return document.getElementById(id); }

  function init() {
    if (initialized) { resize(); reheat(); return; }
    initialized = true;
    canvas = el('graph-canvas'); ctx = canvas.getContext('2d');
    const disSel = el('g-focus-disease');
    [...new Set(Store.catalog.proteins.flatMap(p => p.diseases || []))].sort()
      .forEach(d => disSel.add(new Option(d, d)));
    el('g-show-drug').addEventListener('change', e => { showDrug = e.target.checked; build(); reheat(); });
    disSel.addEventListener('change', e => { focusDisease = e.target.value; build(); reheat(); });
    el('g-reheat').addEventListener('click', reheat);
    bindInteraction();
    window.addEventListener('resize', () => { if (isVisible()) resize(); });
    build(); resize(); reheat();
  }

  function isVisible() { return el('tab-graph').classList.contains('active'); }

  function build() {
    const G = Store.graph;
    const keep = new Set();
    let activeNodes = G.nodes.filter(n => {
      if (!showDrug && n.type === 'drug') return false;
      return true;
    });
    if (focusDisease) {
      const did = 'D:' + focusDisease;
      const nbr = new Set([did]);
      G.links.forEach(l => {
        const s = idOf(l.source), t = idOf(l.target);
        if (s === did) nbr.add(t); if (t === did) nbr.add(s);
      });
      // 단백질의 경로/축도 포함
      G.links.forEach(l => {
        const s = idOf(l.source), t = idOf(l.target);
        if (nbr.has(s) && (t.startsWith('PW:') || t.startsWith('AX:'))) nbr.add(t);
        if (nbr.has(t) && (s.startsWith('PW:') || s.startsWith('AX:'))) nbr.add(s);
      });
      activeNodes = activeNodes.filter(n => nbr.has(n.id));
    }
    activeNodes.forEach(n => keep.add(n.id));
    const prev = {}; nodes.forEach(n => prev[n.id] = n);
    nodes = activeNodes.map(n => {
      const p = prev[n.id];
      return Object.assign({}, n, {
        x: p ? p.x : (Math.random() - .5) * 600, y: p ? p.y : (Math.random() - .5) * 600,
        vx: 0, vy: 0, r: TYPE_R[n.type] + Math.min(6, (n.degree || 1) * 0.5),
      });
    });
    const byId = {}; nodes.forEach(n => byId[n.id] = n);
    links = G.links.filter(l => keep.has(idOf(l.source)) && keep.has(idOf(l.target)))
      .map(l => ({ s: byId[idOf(l.source)], t: byId[idOf(l.target)], type: l.type }));
  }

  function idOf(x) { return typeof x === 'object' ? x.id : x; }

  function resize() {
    const r = canvas.getBoundingClientRect();
    canvas.width = r.width * devicePixelRatio; canvas.height = r.height * devicePixelRatio;
    ctx.setTransform(devicePixelRatio, 0, 0, devicePixelRatio, 0, 0);
    if (!view._init) { view.x = r.width / 2; view.y = r.height / 2; view.k = 0.8; view._init = true; }
    draw();
  }

  function reheat() { alpha = 1; if (!running) loop(); }

  function tick() {
    const k = 0.9; alpha *= 0.985;
    // 반발 (O(n^2), 노드 수 제한적)
    for (let i = 0; i < nodes.length; i++) {
      const a = nodes[i];
      for (let j = i + 1; j < nodes.length; j++) {
        const b = nodes[j];
        let dx = a.x - b.x, dy = a.y - b.y, d2 = dx * dx + dy * dy || 1;
        const f = 900 / d2;
        const d = Math.sqrt(d2);
        const fx = dx / d * f, fy = dy / d * f;
        a.vx += fx; a.vy += fy; b.vx -= fx; b.vy -= fy;
      }
    }
    // 스프링 (엣지)
    links.forEach(l => {
      let dx = l.t.x - l.s.x, dy = l.t.y - l.s.y, d = Math.sqrt(dx * dx + dy * dy) || 1;
      const target = 60, f = (d - target) * 0.02;
      const fx = dx / d * f, fy = dy / d * f;
      l.s.vx += fx; l.s.vy += fy; l.t.vx -= fx; l.t.vy -= fy;
    });
    // 중심 인력 + 감쇠 + 적용
    nodes.forEach(n => {
      if (n === (drag && drag.node)) return;
      n.vx -= n.x * 0.002; n.vy -= n.y * 0.002;
      n.vx *= k; n.vy *= k;
      n.x += n.vx * alpha * 2; n.y += n.vy * alpha * 2;
    });
  }

  function loop() {
    running = true;
    tick(); draw();
    if (alpha > 0.02) raf = requestAnimationFrame(loop);
    else running = false;
  }

  function toScreen(n) { return [view.x + n.x * view.k, view.y + n.y * view.k]; }
  function toWorld(px, py) { return [(px - view.x) / view.k, (py - view.y) / view.k]; }

  function draw() {
    if (!ctx) return;
    const w = canvas.width / devicePixelRatio, h = canvas.height / devicePixelRatio;
    ctx.clearRect(0, 0, w, h);
    // 엣지
    ctx.lineWidth = 0.6;
    links.forEach(l => {
      const [sx, sy] = toScreen(l.s), [tx, ty] = toScreen(l.t);
      ctx.strokeStyle = (hover && (l.s === hover || l.t === hover)) ? 'rgba(102,153,255,.5)' : 'rgba(255,255,255,.06)';
      ctx.beginPath(); ctx.moveTo(sx, sy); ctx.lineTo(tx, ty); ctx.stroke();
    });
    // 노드
    nodes.forEach(n => {
      const [x, y] = toScreen(n), r = n.r * Math.sqrt(view.k);
      ctx.beginPath(); ctx.arc(x, y, r, 0, 7);
      ctx.fillStyle = TYPE_COLOR[n.type] || '#888';
      ctx.globalAlpha = (hover && hover !== n && !isNeighbor(n, hover)) ? 0.25 : 1;
      ctx.fill();
      if (n === hover || n.type === 'disease' || n.type === 'axis' || (n.type === 'protein' && n.in_model && view.k > 0.7)) {
        ctx.globalAlpha = 1; ctx.fillStyle = '#dfe4ff';
        ctx.font = `${Math.max(9, 11 * Math.sqrt(view.k))}px Inter`;
        ctx.fillText(n.label, x + r + 2, y + 3);
      }
      ctx.globalAlpha = 1;
    });
  }

  function isNeighbor(n, h) { return links.some(l => (l.s === n && l.t === h) || (l.t === n && l.s === h)); }

  function nodeAt(px, py) {
    for (let i = nodes.length - 1; i >= 0; i--) {
      const [x, y] = toScreen(nodes[i]); const r = nodes[i].r * Math.sqrt(view.k) + 3;
      if ((px - x) ** 2 + (py - y) ** 2 < r * r) return nodes[i];
    }
    return null;
  }

  function bindInteraction() {
    const tip = el('graph-tooltip');
    canvas.addEventListener('mousemove', e => {
      const rect = canvas.getBoundingClientRect();
      const px = e.clientX - rect.left, py = e.clientY - rect.top;
      if (drag) { drag.moved = true; const [wx, wy] = toWorld(px, py); drag.node.x = wx; drag.node.y = wy; drag.node.vx = drag.node.vy = 0; draw(); return; }
      if (pan) { view.x += e.clientX - pan.x; view.y += e.clientY - pan.y; pan.x = e.clientX; pan.y = e.clientY; draw(); return; }
      const prev = hover;
      const n = nodeAt(px, py); hover = n;
      if (n) {
        canvas.style.cursor = 'pointer';
        tip.classList.remove('hidden');
        tip.style.left = (e.clientX + 14) + 'px'; tip.style.top = (e.clientY + 12) + 'px';
        tip.innerHTML = `<div class="tt-title">${n.label}</div><div class="tt-sub">${ttSub(n)}</div>`;
      } else { canvas.style.cursor = 'grab'; tip.classList.add('hidden'); }
      if (n !== prev) draw();   // hover 변화시에만 재그리기(유휴 redraw 방지)
    });
    canvas.addEventListener('mousedown', e => {
      const rect = canvas.getBoundingClientRect();
      const n = nodeAt(e.clientX - rect.left, e.clientY - rect.top);
      if (n) { drag = { node: n }; reheat(); } else pan = { x: e.clientX, y: e.clientY };
    });
    window.addEventListener('mouseup', () => {
      justDragged = !!(drag && drag.moved);   // 이동을 동반한 드래그였으면 click 무시
      drag = null; pan = null;
    });
    canvas.addEventListener('click', e => {
      if (justDragged) { justDragged = false; return; }  // 드래그 끝의 click 은 네비게이션 금지
      const rect = canvas.getBoundingClientRect();
      const n = nodeAt(e.clientX - rect.left, e.clientY - rect.top);
      if (n && n.type === 'protein') window.openProtein(n.label);
    });
    canvas.addEventListener('wheel', e => {
      e.preventDefault();
      const rect = canvas.getBoundingClientRect();
      const px = e.clientX - rect.left, py = e.clientY - rect.top;
      const [wx, wy] = toWorld(px, py);
      view.k *= e.deltaY < 0 ? 1.1 : 0.9; view.k = Math.max(0.2, Math.min(4, view.k));
      view.x = px - wx * view.k; view.y = py - wy * view.k; draw();
    }, { passive: false });
  }

  function ttSub(n) {
    if (n.type === 'protein') return `${n.pathway || ''} · ${n.role || ''} · 근거 ${n.n_evidence || 0}편${n.in_model ? ' · 트윈 wired' : ''}`;
    if (n.type === 'axis') return '디지털 트윈 상태축';
    return { pathway: '신호 경로', disease: '질환', drug: '약물/물질' }[n.type] || '';
  }

  window.GraphTab = { init };
})();
