/* ============================================================
   timeline.js — 치료 타임라인 시뮬레이션 탭
   업로드한 두피/쥐/원형탈모 사진 위에, 치료 반응을 0~36개월 프로그레스
   바로 표현.
   ── 정직성 원칙 ──────────────────────────────────────────
   · 회복의 '양(magnitude)' = TwinEngine 모델(치료 전 질환 평형 → 치료 후
     평형 밀도). 모델의 정량 출력.
   · 회복의 '속도(timing)'  = 임상시험 문헌의 반응 동역학(lag/tau/shed).
     모델 ODE 의 '일(day)'은 임상시간과 무관(수주 내 평형)하므로 시간축으로
     쓰지 않고 폐기 → 문헌 시간상수로 보정.
        density(t) = baseline + (target − baseline) · f_clinical(t)
   · 사진 변형 = 위 곡선이 구동하는 절차적 캔버스 일러스트(생성형 AI 아님).
     환부 '기하'는 사용자 사진에서, '얼마나·언제'는 모델+문헌. 개별 환자 예측 아님.
   ============================================================ */
(function () {
  'use strict';

  const DISEASES = [
    { key: "Alopecia Areata", ko: "원형 탈모 (AA)", sub: "두피·패치", style: "edge", ivs: ["JAK_inhibitor", "corticosteroid"] },
    { key: "Androgenetic Alopecia", ko: "남성형 탈모 (AGA)", sub: "두피·미만성", style: "diffuse", ivs: ["finasteride", "minoxidil", "dutasteride"] },
    { key: "Chemotherapy-induced Alopecia", ko: "항암 탈모 (CIA)", sub: "전두피", style: "diffuse", ivs: ["minoxidil", "CDK46_inhibitor"] },
    { key: "__mouse__", ko: "쥐 제모 발모 (마우스)", sub: "등쪽·균일", style: "uniform", ivs: ["minoxidil", "wnt_agonist"] },
  ];
  const IV_KO = {
    finasteride: "피나스테리드", dutasteride: "두타스테리드", minoxidil: "미녹시딜",
    JAK_inhibitor: "JAK 억제제", corticosteroid: "코르티코스테로이드",
    CDK46_inhibitor: "CDK4/6 억제제", wnt_agonist: "Wnt 작용제",
  };

  // 임상 반응 동역학(개월). lag=가시반응 지연, tau=특성시간(≈63% 도달), shed=초기 telogen 탈락.
  // ▶ 3가지 증거를 화해(reconcile)시켜 보정 — digital_twin/timeline_calibration.py + 2차 웹스윕:
  //   (1) 데이터적합: CT.gov 실궤적(THRIVE-AA1/2·brepo·ritle SALT 다시점, 5-ARI 24주). JAK 재적합 R²0.94~0.99.
  //   (2) 기계론적 바닥: 가시 발모는 모낭주기(텔로젠 ~2-3개월)에 묶임 — 약물 PK는 빠르나(DHT 수시간) 모발은 느림.
  //       단기시험의 lag~0 적합은 초기 TAHC '굵어짐' 과적합 → 가시 밀도 lag는 ≥~2개월(5-ARI).
  //   (3) 장기: 피나 Kaufman 2년까지 지속상승(tau~6). JAK는 wk4-8 분리(immune→기존모낭 재진입)로 더 빠름.
  //   ⚠ 단일 지수는 (a)AGA 이상성(빠른 굵어짐+느린 밀도), (b)피나 yr2-5 감소, (c)미녹 재퇴행 재현 못 함(한계).
  const TIME_MODEL = {
    finasteride:     { lag: 2.0, tau: 6,   shed: 0.05 },   // telogen 바닥 + Kaufman 2yr (R²0.999)
    dutasteride:     { lag: 2.0, tau: 5,   shed: 0.05 },   // 더 깊은 DHT 억제(scalp 51% vs 41%)·약간 빠름
    minoxidil:       { lag: 1.5, tau: 4,   shed: 0.14 },   // telogen 단축 → 약간 빠름; SULT1A1 응답자 ~40%
    JAK_inhibitor:   { lag: 1.0, tau: 4,   shed: 0 },      // THRIVE wk4-8 분리; tau는 baricitinib 느림 포함 절충
    corticosteroid:  { lag: 1.0, tau: 3,   shed: 0 },
    CDK46_inhibitor: { lag: 1.5, tau: 4,   shed: 0 },      // 데이터 없음(실험적)
    wnt_agonist:     { lag: 1.5, tau: 4,   shed: 0 },      // 데이터 없음(실험적)
  };
  const TIME_DEFAULT = { lag: 2, tau: 4.5, shed: 0 };     // 무처치/기타
  const MOUSE_KIN    = { lag: 0.2, tau: 0.6, shed: 0 };   // 마우스 모주기 ~2-3주(실제로 빠름)
  // 절대밀도 환산 레퍼런스(정상=100%): 한국인 정수리 ~130 hairs/cm² (Yoo 2002·Han 2004 phototrichogram; 백인 ~226).
  const NORMAL_DENSITY = 130;

  const S = {
    img: null, region: null, strokes: null, hairColor: '#34281d',
    disease: DISEASES[0], ivs: ["JAK_inhibitor"],
    baseline: null, target: null, kin: null, ti: 0,
    marking: false, markStart: null, chart: null,
  };
  function el(id) { return document.getElementById(id); }
  function gctx() { return el('tl-canvas').getContext('2d', { willReadFrequently: true }); }

  // 임상 시간축(개월). 초반 telogen-shed 구간은 촘촘, 후반은 분기/연 단위.
  const TIMEPOINTS = [
    { m: 0, l: '0' }, { m: 0.5, l: '2주' }, { m: 1, l: '1개월' }, { m: 2, l: '2개월' },
    { m: 3, l: '3개월' }, { m: 4, l: '4개월' }, { m: 6, l: '6개월' }, { m: 9, l: '9개월' },
    { m: 12, l: '1년' }, { m: 18, l: '1.5년' }, { m: 24, l: '2년' }, { m: 30, l: '2.5년' }, { m: 36, l: '3년' },
  ];
  function curMonth() { return TIMEPOINTS[S.ti].m; }
  function setSliderFill() {
    const sl = el('tl-slider'); if (!sl) return;
    const pct = (S.ti / (TIMEPOINTS.length - 1)) * 100;
    sl.style.background = `linear-gradient(90deg,var(--primary) ${pct}%,var(--line2) ${pct}%)`;
  }

  function init() {
    el('tl-disease').innerHTML = DISEASES.map((d, i) =>
      `<button class="seg-btn${i === 0 ? ' active' : ''}" data-i="${i}">
         <span>${d.ko}</span><span class="seg-sub">${d.sub}</span></button>`).join('');
    el('tl-disease').querySelectorAll('.seg-btn').forEach(b =>
      b.onclick = () => { selectDisease(+b.dataset.i); });

    renderIvs();
    el('tl-file').addEventListener('change', onFile);
    bindCanvasMarking();
    const sl = el('tl-slider');
    sl.max = TIMEPOINTS.length - 1; sl.value = 0; S.ti = 0;
    sl.addEventListener('input', () => { S.ti = +sl.value; setSliderFill(); renderAt(); });
    setSliderFill();
    el('tl-remark') && (el('tl-remark').onclick = () => startMark());
    el('tl-demo') && (el('tl-demo').onclick = loadDemo);
    selectDisease(0);
  }

  function selectDisease(i) {
    S.disease = DISEASES[i];
    el('tl-disease').querySelectorAll('.seg-btn').forEach((b, j) => b.classList.toggle('active', j === i));
    S.ivs = S.disease.ivs.slice(0, 1);
    renderIvs();
    computeCurve();
    if (S.img) { rebuildStrokes(); renderAt(); }
  }

  function renderIvs() {
    el('tl-interventions').innerHTML = S.disease.ivs.map(iv =>
      `<button class="chip${S.ivs.includes(iv) ? ' active' : ''}" data-iv="${iv}">${IV_KO[iv] || iv}</button>`).join('');
    el('tl-interventions').querySelectorAll('.chip').forEach(c =>
      c.onclick = () => {
        const iv = c.dataset.iv;
        if (S.ivs.includes(iv)) S.ivs = S.ivs.filter(x => x !== iv); else S.ivs.push(iv);
        renderIvs(); computeCurve(); if (S.img) renderAt();
      });
  }

  // 회복의 '양' = 모델. baseline(치료 전 질환 평형) / target(치료 후 평형) 밀도를 트윈에서 취득.
  function computeCurve() {
    let y0, diseaseForRun;
    if (S.disease.key === "__mouse__") {
      y0 = TwinEngine.diseaseEquilibrium("Healthy"); y0[6] = 0.08;   // 갓 제모(모발만 바닥)
      diseaseForRun = "Healthy";
    } else {
      y0 = TwinEngine.diseaseEquilibrium(S.disease.key);
      diseaseForRun = S.disease.key;
    }
    const mc = TwinEngine.run(diseaseForRun, S.ivs, { y0, days: 1095 }).states.HairDensity;
    S.baseline = mc[0];                    // 치료 전 밀도 = 모델
    S.target = Math.max.apply(null, mc);   // 치료 후 평형 밀도 = 모델
    S.kin = combineKinetics();             // 회복 속도 = 임상 문헌
    renderChart();
  }

  function combineKinetics() {
    if (S.disease.key === "__mouse__") return MOUSE_KIN;
    if (!S.ivs.length) return TIME_DEFAULT;
    let lag = 99, tau = 99, shed = 0;
    S.ivs.forEach(iv => { const k = TIME_MODEL[iv] || TIME_DEFAULT;
      lag = Math.min(lag, k.lag); tau = Math.min(tau, k.tau); shed = Math.max(shed, k.shed); });
    return { lag, tau, shed };
  }
  // 임상 회복 분율 f(t개월) ∈ [-shed,1]: lag 후 1−exp 상승 + 초기 telogen 탈락 딥.
  function recoveryAtMonth(m) {
    const k = S.kin || TIME_DEFAULT;
    const grow = m <= k.lag ? 0 : 1 - Math.exp(-(m - k.lag) / k.tau);
    const dip = -k.shed * Math.exp(-Math.pow(m - 0.7, 2) / (2 * 0.5 * 0.5));   // ~2-8주 탈락
    return Math.max(-0.25, Math.min(1, grow + dip));
  }
  function densityAtMonth(m) {
    return S.baseline + (S.target - S.baseline) * recoveryAtMonth(m);
  }

  // ── 파일 업로드 ──
  function onFile(e) {
    const f = e.target.files && e.target.files[0];
    if (!f) return;
    const r = new FileReader();
    r.onload = () => loadImageSrc(r.result);
    r.readAsDataURL(f);
  }
  function loadImageSrc(src) {
    const im = new Image();
    im.onload = () => { S.img = im; setupCanvas(); defaultRegion(); rebuildStrokes(); startMark(); renderAt(); el('tl-stage').classList.remove('empty'); };
    im.src = src;
  }
  // 데모: 합성 두피(피부+원형 무모부) — 사진 없을 때 동작 확인용
  function loadDemo() {
    const c = document.createElement('canvas'); c.width = 640; c.height = 480;
    const x = c.getContext('2d');
    x.fillStyle = '#caa987'; x.fillRect(0, 0, 640, 480);
    for (let i = 0; i < 14000; i++) {
      const px = Math.random() * 640, py = Math.random() * 480;
      const d = Math.hypot(px - 320, py - 240);
      if (d < 95) continue;
      x.strokeStyle = `rgba(50,35,24,${0.25 + Math.random() * 0.4})`;
      x.lineWidth = 1; x.beginPath(); x.moveTo(px, py);
      x.lineTo(px + Math.random() * 6 - 3, py + 6 + Math.random() * 5); x.stroke();
    }
    x.fillStyle = 'rgba(214,180,150,.85)'; x.beginPath(); x.ellipse(320, 240, 92, 80, 0, 0, 7); x.fill();
    loadImageSrc(c.toDataURL());
  }

  function setupCanvas() {
    const cv = el('tl-canvas'), maxW = 760;
    let w = S.img.naturalWidth, h = S.img.naturalHeight;
    const sc = Math.min(1, maxW / w); w = Math.round(w * sc); h = Math.round(h * sc);
    cv.width = w; cv.height = h;
  }
  function defaultRegion() {
    const cv = el('tl-canvas');
    if (S.disease.style === 'diffuse') S.region = { cx: cv.width / 2, cy: cv.height * 0.42, rx: cv.width * 0.40, ry: cv.height * 0.34 };
    else if (S.disease.style === 'uniform') S.region = { cx: cv.width / 2, cy: cv.height / 2, rx: cv.width * 0.42, ry: cv.height * 0.34 };
    else S.region = { cx: cv.width / 2, cy: cv.height / 2, rx: cv.width * 0.18, ry: cv.width * 0.18 };
  }

  // ── 환부 마킹 (드래그로 타원) ──
  function startMark() { S.marking = 'await'; el('tl-hint').classList.remove('hidden'); renderAt(); }
  function bindCanvasMarking() {
    const cv = el('tl-canvas');
    const pos = ev => { const r = cv.getBoundingClientRect(); return { x: (ev.clientX - r.left) * cv.width / r.width, y: (ev.clientY - r.top) * cv.height / r.height }; };
    cv.addEventListener('mousedown', ev => { if (!S.img) return; S.marking = true; S.markStart = pos(ev); });
    cv.addEventListener('mousemove', ev => {
      if (S.marking !== true || !S.markStart) return;
      const p = pos(ev);
      S.region = { cx: S.markStart.x, cy: S.markStart.y, rx: Math.max(8, Math.abs(p.x - S.markStart.x)), ry: Math.max(8, Math.abs(p.y - S.markStart.y)) };
      renderAt(true);
    });
    window.addEventListener('mouseup', () => {
      if (S.marking === true && S.region) { S.marking = false; el('tl-hint').classList.add('hidden'); rebuildStrokes(); renderAt(); }
    });
  }

  // ── 절차적 모발 스트로크 사전 생성(안정적 progressive 성장) ──
  function rebuildStrokes() {
    if (!S.region || !S.img) return;
    sampleHairColor();
    const R = S.region, area = Math.PI * R.rx * R.ry;
    const N = Math.max(700, Math.min(5200, Math.round(area / 16)));
    const arr = new Array(N);
    for (let i = 0; i < N; i++) {
      const a = Math.random() * Math.PI * 2, rad = Math.sqrt(Math.random());
      const t = rad;
      arr[i] = {
        x: R.cx + rad * Math.cos(a) * R.rx,
        y: R.cy + rad * Math.sin(a) * R.ry,
        t, thrEdge: 1 - t, thrUniform: Math.random(),
        len: ((R.rx + R.ry) / 2) * (0.035 + Math.random() * 0.05),
        ang: Math.PI / 2 + (Math.random() - 0.5) * 0.9,
        shade: 0.55 + Math.random() * 0.45,
      };
    }
    S.strokes = arr;
  }
  function sampleHairColor() {
    try {
      const cv = el('tl-canvas'), x = gctx();
      x.clearRect(0, 0, cv.width, cv.height); x.drawImage(S.img, 0, 0, cv.width, cv.height);
      const img = x.getImageData(0, 0, cv.width, cv.height).data;
      const R = S.region; let rs = 0, gs = 0, bs = 0, n = 0;
      for (let k = 0; k < 500; k++) {
        const a = Math.random() * Math.PI * 2, rad = 1.08 + Math.random() * 0.22;
        const px = Math.round(R.cx + rad * Math.cos(a) * R.rx), py = Math.round(R.cy + rad * Math.sin(a) * R.ry);
        if (px < 0 || py < 0 || px >= cv.width || py >= cv.height) continue;
        const i = (py * cv.width + px) * 4; rs += img[i]; gs += img[i + 1]; bs += img[i + 2]; n++;
      }
      if (n > 20) { S.hairColor = `rgb(${Math.round(rs / n * 0.75)},${Math.round(gs / n * 0.72)},${Math.round(bs / n * 0.7)})`; }
    } catch (e) { S.hairColor = '#34281d'; }
  }

  // ── 렌더 ──
  function renderAt(dragging) {
    const cv = el('tl-canvas'); if (!cv.width) return;
    const x = gctx();
    x.clearRect(0, 0, cv.width, cv.height);
    if (S.img) x.drawImage(S.img, 0, 0, cv.width, cv.height);
    const R = S.region;
    if (R && S.strokes && !dragging) {
      const r = Math.max(0, recoveryAtMonth(curMonth()));   // 탈락(음수)은 빈 두피로
      const useEdge = S.disease.style === 'edge';
      x.lineCap = 'round';
      for (const s of S.strokes) {
        const thr = useEdge ? s.thrEdge : s.thrUniform;
        if (thr > r) continue;
        x.strokeStyle = shade(S.hairColor, s.shade);
        x.lineWidth = 1.1;
        x.beginPath(); x.moveTo(s.x, s.y);
        x.lineTo(s.x + Math.cos(s.ang) * s.len, s.y + Math.sin(s.ang) * s.len);
        x.stroke();
      }
    }
    if (R) {
      x.save(); x.setLineDash([6, 5]); x.strokeStyle = 'rgba(200,64,31,.55)'; x.lineWidth = 1.5;
      x.beginPath(); x.ellipse(R.cx, R.cy, R.rx, R.ry, 0, 0, Math.PI * 2); x.stroke(); x.restore();
    }
    watermark(x, cv);
    updateReadout();
  }
  function shade(rgb, f) {
    const m = rgb.match(/\d+/g) || [52, 40, 29];
    return `rgb(${Math.round(m[0] * f)},${Math.round(m[1] * f)},${Math.round(m[2] * f)})`;
  }
  function watermark(x, cv) {
    x.save();
    x.font = "12px 'Pretendard Variable', sans-serif"; x.textAlign = 'right';
    x.fillStyle = 'rgba(255,255,255,.82)'; x.strokeStyle = 'rgba(0,0,0,.45)'; x.lineWidth = 3;
    const msg = "모델+문헌 일러스트 · 임상 예측 아님";
    x.strokeText(msg, cv.width - 10, cv.height - 10); x.fillText(msg, cv.width - 10, cv.height - 10);
    x.restore();
  }

  function updateReadout() {
    const tp = TIMEPOINTS[S.ti];
    el('tl-month-label').textContent = tp.l;
    el('tl-month-sub').textContent = tp.m >= 1 ? '· 치료 ' + tp.m + '개월차' : '· 치료 ' + Math.round(tp.m * 30) + '일차';
    if (S.target == null) return;
    const dens = densityAtMonth(tp.m), rec = Math.max(0, recoveryAtMonth(tp.m)) * 100;
    el('tl-density').textContent = dens.toFixed(0) + '% (~' + Math.round(dens / 100 * NORMAL_DENSITY) + '/cm²)';
    el('tl-recovery').textContent = rec.toFixed(0) + '%';
    el('tl-ivs-readout').textContent = S.ivs.length ? S.ivs.map(i => IV_KO[i] || i).join(' + ') : '무처치';
  }

  // 밀도 곡선(임상 개월) — '양=모델, 속도=문헌' 결합 곡선을 보여주는 근거 차트
  function renderChart() {
    const ctx = el('chart-timeline'); if (!ctx || S.target == null) return;
    const labels = TIMEPOINTS.map(p => p.l);
    const vals = TIMEPOINTS.map(p => +densityAtMonth(p.m).toFixed(1));
    const data = { labels, datasets: [{ label: '모발 밀도 %', data: vals, borderColor: '#c8401f', backgroundColor: 'rgba(200,64,31,.10)', fill: true, tension: .3, pointRadius: 2, borderWidth: 2 }] };
    const opts = {
      responsive: true, maintainAspectRatio: false,
      scales: { y: { min: 0, max: 105, ticks: { color: '#6b655a', callback: v => v + '%' }, grid: { color: 'rgba(0,0,0,.08)' } },
                x: { ticks: { color: '#6b655a', maxTicksLimit: 9, autoSkip: true }, grid: { display: false } } },
      plugins: { legend: { display: false } },
    };
    if (S.chart) { S.chart.data = data; S.chart.options = opts; S.chart.update('none'); }
    else S.chart = new Chart(ctx, { type: 'line', data, options: opts });
  }

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