/* ============================================================
   network.js — 단백질 상호작용 네트워크 + 모낭 (3D, 시간축)
   Three.js(WebGL) 로 단백질 신호망과 모낭을 3D 렌더. 마우스 회전,
   조명, 같은 시간 슬라이더로 기전→단백질→모낭 변화를 동기 표시.
   데이터: data/network_dynamics.json
   ============================================================ */
(function () {
  'use strict';
  let data = null, done = false, scen = 'AGA|finasteride', tIdx = 0, playing = false, timer = null;
  const el = id => document.getElementById(id);
  const GCOL = {
    androgen: 0xc8401f, wnt_antag: 0xd9683f, bmp: 0x9a6a12, wnt: 0x1f5d52,
    shh: 0x2f8f7f, dp: 0x2f63c8, hfsc: 0x7a55a8, hair: 0x1f6d3a, immune: 0xb3361b,
  };
  const HEX = { androgen: '#c8401f', wnt_antag: '#d9683f', bmp: '#9a6a12', wnt: '#1f5d52', shh: '#2f8f7f', dp: '#2f63c8', hfsc: '#7a55a8', hair: '#1f6d3a', immune: '#b3361b' };
  // 그룹별 깊이(z) — 드라이버 앞, 산출 뒤
  const GZ = { androgen: 2.6, immune: 2.6, wnt_antag: 1.3, bmp: 0.6, wnt: 0, shh: -0.7, dp: -1.5, hfsc: -2.2, hair: -2.9 };
  // 단백질 → UniProt 가속(AlphaFold 구조 로드용)
  const ACC = {
    AR: 'P10275', SRD5A2: 'P31213', IFNG: 'P01579', CD8A: 'P01732', 'HLA-DQB1': 'P01920', CXCL10: 'P02778',
    DKK1: 'O94907', SFRP1: 'Q8N474', BMP4: 'P12644', ID1: 'P41134', WNT10B: 'O00744', CTNNB1: 'P35222',
    LEF1: 'Q9UJU2', AXIN2: 'Q9Y2T1', SHH: 'Q15465', GLI1: 'P08151', SOX2: 'P48431', VCAN: 'P13611',
    ALPL: 'P05186', KRT15: 'P19012', CD34: 'P28906', LGR5: 'O75473', KRT85: 'P78386', KRT35: 'Q92764',
  };

  let net = null, foll = null, raf = null;   // 3D 컨텍스트
  let mode = 'sphere', nglStage = null, nglComps = {}, nglBuilt = false;

  async function init() {
    if (done) return; done = true;
    try { data = await fetch('data/network_dynamics.json').then(r => r.json()); }
    catch (e) { el('net-wrap').innerHTML = '<p class="panel-subtitle">데이터 로드 실패: ' + e + '</p>'; return; }
    buildControls(); buildLegend();
    if (!window.THREE) {
      el('net-wrap').innerHTML = '<p class="panel-subtitle" style="padding:24px">3D 라이브러리(Three.js) 로드 실패 — 인터넷 연결 확인. (네트워크 데이터는 정상)</p>';
      return;
    }
    setupNet(); setupFoll();
    window.addEventListener('resize', onResize);
    update(); animate();
  }

  function curScen() { return data.scenarios[scen]; }
  function months() { return data.months; }
  function actOf(nid) { return curScen().activity[nid][tIdx]; }
  function gact(group) {
    const ids = data.nodes.filter(n => n.group === group).map(n => n.id);
    if (!ids.length) return 0;
    return ids.reduce((s, id) => s + actOf(id), 0) / ids.length;
  }
  function hashJit(id) { let h = 0; for (let i = 0; i < id.length; i++) h = (h * 31 + id.charCodeAt(i)) % 1000; return (h / 1000 - 0.5) * 1.2; }
  // 단백질 클릭 → 아틀라스 탭 상세(있으면) / 없으면 AlphaFold EBI
  function openProteinSafe(gene) {
    const inCat = window.Store && Store.proteinByGene && Store.proteinByGene[gene];
    if (inCat && typeof window.openProtein === 'function') window.openProtein(gene);
    else if (ACC[gene]) window.open('https://alphafold.ebi.ac.uk/entry/' + ACC[gene], '_blank');
  }
  let _down = null;   // 드래그 구분

  // ---------- 컨트롤 ----------
  function buildControls() {
    const diseases = { AGA: '남성형 탈모(AGA)', AA: '원형 탈모(AA)' };
    const dz = el('net-disease');
    dz.innerHTML = Object.keys(diseases).map(d => `<option value="${d}">${diseases[d]}</option>`).join('');
    dz.value = scen.split('|')[0];
    dz.onchange = () => fillTreatments(dz.value);
    fillTreatments(dz.value);
    const sl = el('net-time'); sl.min = 0; sl.max = months().length - 1; sl.value = tIdx;
    sl.oninput = () => { stop(); tIdx = +sl.value; update(); };
    el('net-play').onclick = togglePlay;
    const mb = el('net-mode'); if (mb) mb.onclick = toggleMode;
  }

  function toggleMode() {
    if (typeof NGL === 'undefined') { el('net-status').textContent = 'NGL(구조 뷰어) 미로딩 — 인터넷 확인'; return; }
    mode = mode === 'sphere' ? 'structure' : 'sphere';
    const mb = el('net-mode');
    if (mode === 'structure') {
      mb.textContent = '⚪ 구체(추상)'; mb.classList.add('on');
      el('net-canvas').style.display = 'none'; el('net-ngl').style.display = 'block';
      if (!nglBuilt) buildStructureNet();
    } else {
      mb.textContent = '🧬 AlphaFold 구조'; mb.classList.remove('on');
      el('net-ngl').style.display = 'none'; el('net-canvas').style.display = 'block';
    }
    update();
  }
  function fillTreatments(dz) {
    const keys = Object.keys(data.scenarios).filter(k => k.startsWith(dz + '|'));
    const tr = el('net-treat');
    tr.innerHTML = keys.map(k => `<option value="${k}">${data.scenarios[k].label.split('·')[1].trim()}</option>`).join('');
    scen = keys.includes(scen) ? scen : keys[Math.min(1, keys.length - 1)];
    tr.value = scen;
    tr.onchange = () => { stop(); scen = tr.value; update(); };
  }
  function buildLegend() {
    el('net-legend').innerHTML = Object.keys(data.groups).map(g =>
      `<span class="net-leg"><i style="background:${HEX[g]}"></i>${data.groups[g]}</span>`).join('')
      + '<span class="net-leg" style="margin-left:auto">🖱 드래그=회전·휠=확대 · <b>단백질 클릭 → 아틀라스 상세</b></span>';
  }
  function togglePlay() { playing ? stop() : start(); }
  function start() {
    playing = true; el('net-play').textContent = '⏸ 정지';
    timer = setInterval(() => {
      tIdx++; if (tIdx >= months().length) { tIdx = months().length - 1; stop(); return; }
      el('net-time').value = tIdx; update();
    }, 720);
  }
  function stop() { playing = false; el('net-play').textContent = '▶ 재생'; if (timer) clearInterval(timer); timer = null; }

  // ---------- 3D 공통 ----------
  function mkCtx(canvas, camPos) {
    const w = canvas.clientWidth || 400, h = canvas.clientHeight || 360;
    const renderer = new THREE.WebGLRenderer({ canvas, antialias: true, alpha: true });
    renderer.setPixelRatio(Math.min(2, window.devicePixelRatio || 1));
    renderer.setSize(w, h, false);
    const scene = new THREE.Scene();
    const cam = new THREE.PerspectiveCamera(45, w / h, 0.1, 100);
    cam.position.set(camPos[0], camPos[1], camPos[2]);
    const ctrl = new THREE.OrbitControls(cam, renderer.domElement);
    ctrl.enableDamping = true; ctrl.dampingFactor = 0.08; ctrl.enablePan = false;
    scene.add(new THREE.AmbientLight(0xffffff, 0.62));
    const d1 = new THREE.DirectionalLight(0xfff4e6, 0.85); d1.position.set(5, 8, 9); scene.add(d1);
    const d2 = new THREE.DirectionalLight(0xcfe0ff, 0.32); d2.position.set(-6, -3, -5); scene.add(d2);
    return { renderer, scene, cam, ctrl, w, h };
  }
  function edgeCyl(a, b, color, radius, op) {
    const dir = new THREE.Vector3().subVectors(b, a); const len = dir.length();
    const geo = new THREE.CylinderGeometry(radius, radius, len, 6);
    const mat = new THREE.MeshStandardMaterial({ color, emissive: color, emissiveIntensity: .35, transparent: true, opacity: op, roughness: .6 });
    const m = new THREE.Mesh(geo, mat);
    m.position.copy(a).addScaledVector(dir, .5);
    m.quaternion.setFromUnitVectors(new THREE.Vector3(0, 1, 0), dir.clone().normalize());
    return m;
  }

  // ---------- 네트워크 3D ----------
  function nodePos(n) {
    return new THREE.Vector3((n.x - 50) / 50 * 8, (50 - n.y) / 50 * 5, (GZ[n.group] || 0) + hashJit(n.id));
  }
  function setupNet() {
    net = mkCtx(el('net-canvas'), [0, 0, 18]);
    net.ctrl.target.set(0, 0, 0); net.ctrl.autoRotate = true; net.ctrl.autoRotateSpeed = 0.5;
    net.nodes = {}; net.edges = [];
    data.nodes.forEach(n => {
      const geo = new THREE.SphereGeometry(0.42, 22, 22);
      const mat = new THREE.MeshStandardMaterial({ color: GCOL[n.group], emissive: GCOL[n.group], emissiveIntensity: .2, roughness: .35, metalness: .1 });
      const mesh = new THREE.Mesh(geo, mat); mesh.position.copy(nodePos(n));
      mesh.userData.id = n.id;
      net.scene.add(mesh); net.nodes[n.id] = mesh;
      // 라벨 스프라이트
      const sp = makeLabel(n.label); sp.position.copy(mesh.position).add(new THREE.Vector3(0, 0.85, 0));
      net.scene.add(sp);
    });
    data.edges.forEach(e => {
      const a = nodePos(data.nodes.find(n => n.id === e.from)), b = nodePos(data.nodes.find(n => n.id === e.to));
      if (!a || !b) return;
      const col = e.type === 'inhibit' ? 0xb3361b : 0x1f6d3a;
      const cyl = edgeCyl(a, b, col, 0.05, 0.5); cyl.userData = e; net.scene.add(cyl); net.edges.push(cyl);
    });
    // 클릭 → 단백질 상세 (드래그 제외)
    const cv = el('net-canvas'); cv.style.cursor = 'pointer';
    cv.addEventListener('pointerdown', e => { _down = [e.clientX, e.clientY]; });
    cv.addEventListener('pointerup', e => {
      if (mode !== 'sphere' || !net || !_down) return;
      if (Math.hypot(e.clientX - _down[0], e.clientY - _down[1]) > 6) { _down = null; return; }
      _down = null;
      const r = cv.getBoundingClientRect();
      const m = new THREE.Vector2(((e.clientX - r.left) / r.width) * 2 - 1, -((e.clientY - r.top) / r.height) * 2 + 1);
      const ray = new THREE.Raycaster(); ray.setFromCamera(m, net.cam);
      const hits = ray.intersectObjects(Object.values(net.nodes));
      if (hits.length && hits[0].object.userData.id) openProteinSafe(hits[0].object.userData.id);
    });
  }
  function makeLabel(text) {
    const cv = document.createElement('canvas'); const s = 128; cv.width = 256; cv.height = 64;
    const g = cv.getContext('2d'); g.font = '700 30px Pretendard, sans-serif'; g.fillStyle = '#1b1a16';
    g.textAlign = 'center'; g.textBaseline = 'middle'; g.fillText(text, 128, 34);
    const tex = new THREE.CanvasTexture(cv); tex.anisotropy = 4;
    const sp = new THREE.Sprite(new THREE.SpriteMaterial({ map: tex, transparent: true, depthTest: false }));
    sp.scale.set(2.4, 0.6, 1); return sp;
  }
  function updateNet() {
    if (!net) return;
    data.nodes.forEach(n => {
      const a = actOf(n.id), m = net.nodes[n.id];
      const s = 0.55 + 1.05 * a; m.scale.setScalar(s);
      m.material.emissiveIntensity = 0.12 + 0.95 * a;
      m.material.opacity = 1; m.material.transparent = false;
    });
    net.edges.forEach(c => { const sa = actOf(c.userData.from); c.material.opacity = 0.1 + 0.6 * sa; });
  }

  // ---------- AlphaFold 구조 네트워크 (NGL) ----------
  const KPOS = 17, SSCALE = 0.5;
  function posK(n) { return [(n.x - 50) / 50 * 8 * KPOS, (50 - n.y) / 50 * 5 * KPOS, ((GZ[n.group] || 0) + hashJit(n.id)) * KPOS]; }
  function structUrls(acc) { return ['../digital_twin/data/structures/' + acc + '_AF.pdb', 'https://alphafold.ebi.ac.uk/files/AF-' + acc + '-F1-model_v4.pdb']; }

  function buildStructureNet() {
    nglBuilt = true;
    const load = el('net-ngl-load'); load.style.display = 'flex';
    nglStage = new NGL.Stage('net-ngl', { backgroundColor: '#f2ece1' });
    el('net-ngl').style.cursor = 'pointer';
    const compId = {};   // uuid → gene
    nglStage.signals.clicked.add(pp => { if (pp && pp.component && compId[pp.component.uuid]) openProteinSafe(compId[pp.component.uuid]); });
    const nodesA = data.nodes.filter(n => ACC[n.id]); const total = nodesA.length; let loaded = 0;
    load.textContent = 'AlphaFold 구조 로딩 0/' + total;
    // 엣지 + 라벨(정적)
    const shape = new NGL.Shape('edges');
    data.edges.forEach(e => {
      const a = data.nodes.find(n => n.id === e.from), b = data.nodes.find(n => n.id === e.to);
      if (!a || !b || !ACC[a.id] || !ACC[b.id]) return;
      const col = e.type === 'inhibit' ? [0.7, 0.21, 0.11] : [0.12, 0.43, 0.23];
      try { shape.addCylinder(posK(a), posK(b), col, 0.9); } catch (er) {}
    });
    nodesA.forEach(n => { const p = posK(n); try { shape.addText([p[0], p[1] + 16, p[2]], [0.1, 0.09, 0.08], 15, n.label); } catch (er) {} });
    try { nglStage.addComponentFromObject(shape).addRepresentation('buffer'); } catch (er) {}
    // 구조
    nodesA.forEach(n => {
      const urls = structUrls(ACC[n.id]);
      const place = comp => {
        const r = comp.addRepresentation('cartoon', { color: HEX[n.group], smoothSheet: true });
        let cx = 0, cy = 0, cz = 0;
        try { const c = comp.structure.center; cx = c.x; cy = c.y; cz = c.z; } catch (e) {}
        comp.setScale(SSCALE);
        const p = posK(n);
        comp.setPosition([p[0] - SSCALE * cx, p[1] - SSCALE * cy, p[2] - SSCALE * cz]);
        compId[comp.uuid] = n.id;
        nglComps[n.id] = { comp, repr: r };
      };
      const fin = () => { loaded++; load.textContent = 'AlphaFold 구조 로딩 ' + loaded + '/' + total; if (loaded >= total) { load.style.display = 'none'; try { nglStage.autoView(600); } catch (e) {} updateStructureNet(); } };
      nglStage.loadFile(urls[0], { ext: 'pdb' }).then(c => { place(c); fin(); })
        .catch(() => nglStage.loadFile(urls[1], { ext: 'pdb' }).then(c => { place(c); fin(); }).catch(fin));
    });
  }
  function updateStructureNet() {
    data.nodes.forEach(n => {
      const c = nglComps[n.id]; if (!c) return;
      const a = actOf(n.id);
      try { c.repr.setParameters({ opacity: 0.45 + 0.55 * a }); } catch (e) {}
    });
  }

  // ---------- 모낭 3D ----------
  function setupFoll() {
    foll = mkCtx(el('net-follicle'), [0, -0.5, 12]);
    foll.ctrl.target.set(0, -1, 0); foll.ctrl.autoRotate = true; foll.ctrl.autoRotateSpeed = 0.8;
    // 피부층(반투명 디스크)
    const layer = (y, h, r, col, op) => {
      const m = new THREE.Mesh(new THREE.CylinderGeometry(r, r, h, 40),
        new THREE.MeshStandardMaterial({ color: col, transparent: true, opacity: op, roughness: .9 }));
      m.position.y = y; return m;
    };
    foll.scene.add(layer(2.1, 0.5, 4, 0xeac9a6, 0.55));   // 표피
    foll.scene.add(layer(-0.3, 4.2, 4, 0xe3cdb0, 0.34));  // 진피
    foll.scene.add(layer(-3.1, 1.4, 4, 0xf0dd9c, 0.4));   // 피하지방
    foll.group = new THREE.Group(); foll.scene.add(foll.group);
  }
  function buildFollicle(hair, dp, immune, disease) {
    const g = new THREE.Group();
    const surf = 2.0;
    const depth = disease === 'AA' ? (4.6 + 0.6 * hair) : (2.6 + 3.4 * dp);
    const bulbY = surf - depth;
    const bulbR = 0.45 + 0.7 * (disease === 'AA' ? 0.8 : dp);
    const neckR = 0.22 + 0.18 * (disease === 'AA' ? 0.8 : dp);
    // 외초 — LatheGeometry(프로파일 회전)
    const pts = [
      new THREE.Vector2(0.02, 0), new THREE.Vector2(bulbR, 0.35 * depth * 0.3 + 0.2),
      new THREE.Vector2(bulbR * 0.7, depth * 0.45), new THREE.Vector2(neckR, depth * 0.7),
      new THREE.Vector2(neckR, depth),
    ];
    const sheath = new THREE.Mesh(new THREE.LatheGeometry(pts, 36),
      new THREE.MeshStandardMaterial({ color: 0xe6c9a4, transparent: true, opacity: 0.55, side: THREE.DoubleSide, roughness: .8 }));
    sheath.position.y = bulbY; g.add(sheath);
    // 진피유두 DP
    const dpR = 0.22 + 0.5 * dp;
    const dpm = new THREE.Mesh(new THREE.SphereGeometry(dpR, 20, 20),
      new THREE.MeshStandardMaterial({ color: 0xc98b5e, emissive: 0x6b3a1e, emissiveIntensity: .25, roughness: .5 }));
    dpm.position.y = bulbY + bulbR * 0.5; g.add(dpm);
    // 모간 hair shaft
    const hairR = disease === 'AA' ? 0.14 : (0.04 + 0.26 * hair);
    const hairAbove = 0.4 + 3.2 * hair;
    const shaftBottom = bulbY + bulbR * 0.4, shaftTop = surf + hairAbove;
    const aaBroken = (disease === 'AA' && hair < 0.45);
    const top = aaBroken ? surf - 0.2 + 0.4 * hair : shaftTop;
    const len = Math.max(0.2, top - shaftBottom);
    const hairCol = disease === 'AA' ? (hair > 0.5 ? 0x3f3020 : 0x6b5640) : (hair > 0.5 ? 0x402f1c : 0x8a7660);
    const shaft = new THREE.Mesh(new THREE.CylinderGeometry(hairR, hairR * 1.2, len, 12),
      new THREE.MeshStandardMaterial({ color: hairCol, roughness: .55, metalness: .15 }));
    shaft.position.y = shaftBottom + len / 2; g.add(shaft);
    // 피지선
    const sg = new THREE.Mesh(new THREE.SphereGeometry(0.3, 14, 14),
      new THREE.MeshStandardMaterial({ color: 0xdcc878, transparent: true, opacity: .7, roughness: .8 }));
    sg.position.set(neckR + 0.35, surf - depth * 0.28, 0); g.add(sg);
    // 면역세포(AA)
    if (disease === 'AA') {
      const n = Math.round(immune * 14);
      for (let i = 0; i < n; i++) {
        const a = i / Math.max(n, 1) * 6.283, rr = bulbR + 0.45 + (i % 3) * 0.18;
        const c = new THREE.Mesh(new THREE.SphereGeometry(0.13, 10, 10),
          new THREE.MeshStandardMaterial({ color: 0xb3361b, emissive: 0x7a1c0c, emissiveIntensity: .4 }));
        c.position.set(Math.cos(a) * rr, bulbY + bulbR * 0.5 + Math.sin(a * 1.7) * 0.5, Math.sin(a) * rr);
        g.add(c);
      }
    }
    return g;
  }
  function updateFoll() {
    if (!foll) return;
    const disease = scen.split('|')[0];
    while (foll.group.children.length) { const c = foll.group.children.pop(); c.traverse && c.traverse(o => { o.geometry && o.geometry.dispose(); o.material && o.material.dispose(); }); foll.group.remove(c); }
    foll.group.add(buildFollicle(gact('hair'), gact('dp'), gact('immune'), disease));
    let state;
    if (disease === 'AA') { const im = gact('immune'), h = gact('hair'); state = im > 0.55 ? '면역공격 · 모발탈락' : (h > 0.6 ? '재생 완료' : '재생 중'); }
    else { const h = gact('hair'); state = h < 0.32 ? '소형화 모낭(vellus)' : (h < 0.65 ? '회복 중' : '정상모(terminal)'); }
    el('net-foll-state').textContent = state;
  }

  // ---------- 갱신/루프 ----------
  function update() {
    const m = months()[tIdx];
    el('net-month').textContent = m < 1 ? Math.round(m * 30) + '일' : m + '개월';
    const S = curScen();
    el('net-status').textContent = S.label + ' — 표적: ' + (S.targets && S.targets.length ? S.targets.map(t => data.groups[t]).join(', ') : '없음(무치료)');
    if (mode === 'structure' && nglBuilt) updateStructureNet(); else updateNet();
    updateFoll();
  }
  function onResize() {
    [net, foll].forEach(c => { if (!c) return; const cv = c.renderer.domElement; const w = cv.clientWidth, h = cv.clientHeight; if (w && h) { c.renderer.setSize(w, h, false); c.cam.aspect = w / h; c.cam.updateProjectionMatrix(); } });
  }
  function animate() {
    raf = requestAnimationFrame(animate);
    if (net) { net.ctrl.update(); net.renderer.render(net.scene, net.cam); }
    if (foll) { foll.ctrl.update(); foll.renderer.render(foll.scene, foll.cam); }
  }

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