// viz.jsx — 공유 시각화 컴포넌트
// 시계 다이얼, 트리맵, 레이스 차트, 점 타임라인, 목표 링 등

const { useState: useVizState, useMemo: useVizMemo, useRef: useVizRef, useEffect: useVizEffect } = React;

/* ────────────────────────────────────────────────────────────
   ClockDial — 24시간 시계 다이얼
   jobs: [{ t: Date, label, color, key }]
   ──────────────────────────────────────────────────────────── */
function ClockDial({ now, jobs = [], size = 220 }) {
  const cx_ = size / 2, cy = size / 2;
  const rOuter = size / 2 - 6;
  const rInner = rOuter - 8;
  const rTick = rOuter - 4;
  const rJob = rInner - 14;

  const angleOf = (date) => {
    const baseDate = new Date(date);
    const h = baseDate.getHours() + baseDate.getMinutes()/60;
    return (h / 24) * 2 * Math.PI - Math.PI/2;
  };
  const angleNow = angleOf(now);
  const nowX = cx_ + (rInner - 6) * Math.cos(angleNow);
  const nowY = cy + (rInner - 6) * Math.sin(angleNow);

  // hours: 6, 12, 18 labels
  const labels = [
    { h: 0,  txt: '0' },  { h: 6, txt: '6' },
    { h: 12, txt: '12' }, { h: 18, txt: '18' },
  ];

  const [hover, setHover] = useVizState(null);

  return (
    <div className="dial-wrap" style={{ width: size, height: size, position: 'relative', margin: '0 auto' }}>
      <svg width={size} height={size} viewBox={`0 0 ${size} ${size}`}>
        {/* outer ring */}
        <circle cx={cx_} cy={cy} r={rOuter} fill="none" stroke="var(--border-subtle)" strokeWidth="1" />
        <circle cx={cx_} cy={cy} r={rInner} fill="none" stroke="var(--border)" strokeWidth="1" strokeDasharray="1 4" opacity="0.55" />

        {/* hour ticks every 1h */}
        {Array.from({length: 24}, (_, i) => {
          const a = (i/24)*2*Math.PI - Math.PI/2;
          const major = i % 6 === 0;
          const x1 = cx_ + (rTick - (major ? 6 : 3)) * Math.cos(a);
          const y1 = cy + (rTick - (major ? 6 : 3)) * Math.sin(a);
          const x2 = cx_ + rTick * Math.cos(a);
          const y2 = cy + rTick * Math.sin(a);
          return <line key={i} x1={x1} y1={y1} x2={x2} y2={y2} stroke="var(--text-4)" strokeWidth={major ? 1.2 : 0.5} opacity={major ? 0.6 : 0.4} />;
        })}

        {/* labels */}
        {labels.map(l => {
          const a = (l.h/24)*2*Math.PI - Math.PI/2;
          const x = cx_ + (rTick - 18) * Math.cos(a);
          const y = cy + (rTick - 18) * Math.sin(a);
          return (
            <text key={l.h} x={x} y={y} textAnchor="middle" dy="0.34em" fontSize="10.5" fill="var(--text-3)" style={{ fontFamily: 'var(--font-mono)' }}>
              {l.txt}
            </text>
          );
        })}

        {/* job arcs/markers */}
        {jobs.map((j, i) => {
          const a = angleOf(j.t);
          const x = cx_ + rJob * Math.cos(a);
          const y = cy + rJob * Math.sin(a);
          const isFuture = (+new Date(j.t) > +new Date(now));
          return (
            <g key={i}
               onMouseEnter={() => setHover(i)} onMouseLeave={() => setHover(null)}
               onTouchStart={() => setHover(i)} style={{ cursor: 'pointer' }}>
              {/* line from inner ring to job marker */}
              <line x1={cx_ + (rTick - 10) * Math.cos(a)} y1={cy + (rTick - 10) * Math.sin(a)}
                    x2={x} y2={y} stroke={j.color} strokeWidth="1" opacity={isFuture ? 0.8 : 0.3} />
              <circle cx={x} cy={y} r={hover === i ? 7 : 5}
                      fill={j.color} stroke="var(--bg-elev-1)" strokeWidth="1.5"
                      opacity={isFuture ? 1 : 0.45}
                      style={{ transition: 'r .15s' }} />
            </g>
          );
        })}

        {/* now hand */}
        <line x1={cx_} y1={cy} x2={nowX} y2={nowY} stroke="var(--accent)" strokeWidth="1.6" strokeLinecap="round" />
        <circle cx={cx_} cy={cy} r="3.5" fill="var(--accent)" />
        <circle cx={cx_} cy={cy} r="7" fill="none" stroke="var(--accent)" strokeWidth="1" opacity="0.3" />
      </svg>

      {hover != null && (
        <div className="dial-tip">
          <div className="dial-tip-label">{jobs[hover].label}</div>
          <div className="dial-tip-time mono">{fmtTimeShort(new Date(jobs[hover].t))} · {relTime(new Date(jobs[hover].t))}</div>
        </div>
      )}
    </div>
  );
}

/* ────────────────────────────────────────────────────────────
   Treemap — 사각형 자산 분포
   slices: [{ label, value, color, market, sym? }]
   ──────────────────────────────────────────────────────────── */
function Treemap({ slices, width = 600, height = 320, onCellClick }) {
  // squarified treemap (simple)
  const total = slices.reduce((a, b) => a + b.value, 0) || 1;
  const sorted = [...slices].sort((a, b) => b.value - a.value);

  // Build using simple slice-and-dice with row balancing
  const cells = useVizMemo(() => {
    const result = [];
    const layout = (items, x, y, w, h) => {
      if (items.length === 0) return;
      if (items.length === 1) {
        result.push({ ...items[0], x, y, w, h });
        return;
      }
      const sum = items.reduce((a, b) => a + b.value, 0);
      // split into 2 groups
      let acc = 0, split = 0;
      for (let i = 0; i < items.length; i++) {
        acc += items[i].value;
        if (acc >= sum / 2) { split = i + 1; break; }
      }
      split = Math.max(1, Math.min(items.length - 1, split));
      const g1 = items.slice(0, split);
      const g2 = items.slice(split);
      const r1 = g1.reduce((a,b)=>a+b.value,0) / sum;
      if (w >= h) {
        const w1 = w * r1;
        layout(g1, x, y, w1, h);
        layout(g2, x + w1, y, w - w1, h);
      } else {
        const h1 = h * r1;
        layout(g1, x, y, w, h1);
        layout(g2, x, y + h1, w, h - h1);
      }
    };
    layout(sorted, 0, 0, width, height);
    return result;
  }, [sorted, width, height]);

  return (
    <div className="treemap" style={{ position: 'relative', width: '100%', aspectRatio: `${width}/${height}` }}>
      <svg viewBox={`0 0 ${width} ${height}`} preserveAspectRatio="none" width="100%" height="100%" style={{ display: 'block' }}>
        {cells.map((c, i) => {
          const pct = (c.value / total) * 100;
          const showLabel = c.w > 60 && c.h > 36;
          const showVal = c.w > 80 && c.h > 56;
          return (
            <g key={i} onClick={() => onCellClick?.(c)} style={{ cursor: onCellClick ? 'pointer' : 'default' }}>
              <rect x={c.x + 1} y={c.y + 1} width={Math.max(0, c.w - 2)} height={Math.max(0, c.h - 2)}
                    fill={c.color} stroke="var(--bg)" strokeWidth="2"
                    rx="4" ry="4" opacity={c.foreground ? 1 : 0.85}>
                <title>{c.label} — {pct.toFixed(1)}%</title>
              </rect>
              {showLabel && (
                <>
                  <text x={c.x + 10} y={c.y + 18} fontSize="11" fontWeight="600" fill={c.dark ? 'var(--text)' : 'oklch(0.99 0 0)'} style={{ letterSpacing: '-0.005em' }}>
                    {c.label}
                  </text>
                  <text x={c.x + 10} y={c.y + 32} fontSize="10" fill={c.dark ? 'var(--text-2)' : 'oklch(0.99 0 0 / 0.85)'} style={{ fontFamily: 'var(--font-mono)' }}>
                    {pct.toFixed(1)}%
                  </text>
                  {showVal && (
                    <text x={c.x + 10} y={c.y + c.h - 12} fontSize="11" fill={c.dark ? 'var(--text-3)' : 'oklch(0.99 0 0 / 0.78)'} style={{ fontFamily: 'var(--font-mono)', fontVariantNumeric: 'tabular-nums' }}>
                      {fmtKRW(c.value)}원
                    </text>
                  )}
                </>
              )}
            </g>
          );
        })}
      </svg>
    </div>
  );
}

/* ────────────────────────────────────────────────────────────
   GoalRing — 목표 진행 큰 링 (트랙)
   target: { current, goal, currency, label, prev, mode }
   ──────────────────────────────────────────────────────────── */
function GoalRing({ current, goal, prev = 0, label, sub, currency = 'KRW', size = 180, color = 'var(--accent)' }) {
  const range = goal - prev;
  const progress = Math.max(0, Math.min(1, (current - prev) / (range || 1)));
  const pct = progress * 100;

  const r = (size - 14) / 2;
  const c = 2 * Math.PI * r;
  const offset = c * (1 - progress);

  const fmt = (n) => currency === 'KRW' ? fmtKRW(n) + '원' : '$' + fmtUSD(n);
  const reached = current >= goal;

  return (
    <div className="goal-ring" style={{ width: size, height: size, position: 'relative' }}>
      <svg width={size} height={size}>
        {/* track */}
        <circle cx={size/2} cy={size/2} r={r} fill="none" stroke="var(--bg-inset)" strokeWidth="9" />
        {/* progress */}
        <circle
          cx={size/2} cy={size/2} r={r} fill="none"
          stroke={color} strokeWidth="9" strokeLinecap="round"
          strokeDasharray={c} strokeDashoffset={offset}
          transform={`rotate(-90 ${size/2} ${size/2})`}
          style={{ transition: 'stroke-dashoffset .8s var(--ease-out)' }}
        />
        {/* tick at 0 */}
        <circle cx={size/2} cy={14/2 + 2} r="2.5" fill="var(--text-3)" />
        {/* tick at goal (top) */}
        {reached && <circle cx={size/2} cy={14/2 + 2} r="3" fill={color} />}
      </svg>
      <div style={{
        position: 'absolute', inset: 0, display: 'flex', flexDirection: 'column',
        alignItems: 'center', justifyContent: 'center', textAlign: 'center', padding: '0 20px',
      }}>
        {label && <div style={{ fontSize: 10.5, color: 'var(--text-3)', letterSpacing: '0.02em', textTransform: 'uppercase' }}>{label}</div>}
        <div className="mono tnum" style={{ fontSize: size*0.18, fontWeight: 600, letterSpacing: '-0.02em', lineHeight: 1.1, marginTop: 4, color: 'var(--text)' }}>
          {Math.round(pct)}%
        </div>
        <div className="mono tnum" style={{ fontSize: 10.5, color: 'var(--text-3)', marginTop: 4, letterSpacing: 0 }}>
          {fmt(current)}
        </div>
        <div className="mono" style={{ fontSize: 10, color: 'var(--text-4)', marginTop: 2, letterSpacing: 0 }}>
          / {fmt(goal)}
        </div>
      </div>
    </div>
  );
}

/* ────────────────────────────────────────────────────────────
   GoalLadder — 사다리: 다음 5개 목표
   ──────────────────────────────────────────────────────────── */
function GoalLadder({ current, goals, currency = 'KRW' }) {
  // goals: [number...] ascending
  const fmt = (n) => currency === 'KRW' ? fmtKRW(n) + '원' : '$' + fmtUSD(n);
  const reachedIdx = goals.findIndex(g => current < g);
  const targetIdx = reachedIdx < 0 ? goals.length - 1 : reachedIdx;

  return (
    <div className="ladder">
      {goals.map((g, i) => {
        const isReached = current >= g;
        const isCurrent = i === targetIdx && !isReached;
        return (
          <div key={i} className={cx('ladder-step', isReached && 'reached', isCurrent && 'current')}>
            <span className="ladder-rung" />
            <span className="ladder-label mono tnum">{fmt(g)}</span>
            {isReached && <span className="ladder-check mono">✓</span>}
            {isCurrent && (
              <span className="ladder-progress">
                <span className="ladder-progress-fill" style={{ width: `${Math.max(2, Math.min(100, ((current - (goals[i-1] || 0)) / (g - (goals[i-1] || 0))) * 100))}%` }} />
              </span>
            )}
          </div>
        );
      })}
    </div>
  );
}

/* ────────────────────────────────────────────────────────────
   DotTimeline — 가로축 시간, 세로축 심각도, 컬러 점
   items: [{ t: Date, sev: 'CRITICAL'|'ERROR'|'WARN'|'INFO', id }]
   ──────────────────────────────────────────────────────────── */
function DotTimeline({ items, now, range = '24h', height = 120, onDotClick }) {
  const sevLanes = ['CRITICAL', 'ERROR', 'WARN', 'INFO'];
  const sevColor = {
    CRITICAL: 'var(--critical)',
    ERROR: 'var(--buy)',
    WARN: 'var(--warn)',
    INFO: 'var(--info)',
  };
  const hours = { '24h': 24, '7d': 24*7, '30d': 24*30 }[range] || 24;
  const ms = hours * 3600 * 1000;
  const tEnd = +new Date(now);
  const tStart = tEnd - ms;
  const visible = items.filter(p => +p.t >= tStart && +p.t <= tEnd);

  const padL = 56, padR = 12, padT = 8, padB = 24;

  // ticks
  const ticks = useVizMemo(() => {
    if (range === '24h') {
      const arr = [];
      for (let h = 0; h <= 24; h += 4) {
        const t = tEnd - h * 3600 * 1000;
        arr.push({ t, label: `${h}h` });
      }
      return arr.reverse();
    } else if (range === '7d') {
      const arr = [];
      for (let d = 0; d <= 7; d += 1) {
        const t = tEnd - d * 24*3600*1000;
        arr.push({ t, label: `${d}d` });
      }
      return arr.reverse();
    } else {
      const arr = [];
      for (let d = 0; d <= 30; d += 5) {
        const t = tEnd - d * 24*3600*1000;
        arr.push({ t, label: `${d}d` });
      }
      return arr.reverse();
    }
  }, [range, tEnd]);

  return (
    <div className="dot-timeline">
      <svg width="100%" height={height} viewBox={`0 0 800 ${height}`} preserveAspectRatio="none" style={{ display: 'block' }}>
        {/* lanes */}
        {sevLanes.map((s, i) => {
          const y = padT + ((i + 0.5) / sevLanes.length) * (height - padT - padB);
          return (
            <g key={s}>
              <line x1={padL} y1={y} x2={800 - padR} y2={y} stroke="var(--border-subtle)" strokeWidth="0.5" strokeDasharray="2 4" />
              <text x={padL - 8} y={y + 3} textAnchor="end" fontSize="9.5" fill={sevColor[s]} fontWeight="700" letterSpacing="0.05em" style={{ fontFamily: 'var(--font-mono)' }}>
                {s}
              </text>
            </g>
          );
        })}
        {/* x-axis ticks */}
        {ticks.map((tk, i) => {
          const x = padL + ((tk.t - tStart) / ms) * (800 - padL - padR);
          return (
            <g key={i}>
              <line x1={x} y1={height - padB + 2} x2={x} y2={height - padB + 6} stroke="var(--text-4)" strokeWidth="1" />
              <text x={x} y={height - padB + 16} fontSize="9" fill="var(--text-4)" textAnchor="middle" style={{ fontFamily: 'var(--font-mono)' }}>
                {tk.label}
              </text>
            </g>
          );
        })}
        {/* now line */}
        <line x1={800 - padR} y1={padT} x2={800 - padR} y2={height - padB} stroke="var(--accent)" strokeWidth="1" strokeDasharray="2 3" opacity="0.8" />
        <text x={800 - padR + 3} y={padT + 8} fontSize="9" fill="var(--accent)" textAnchor="start" style={{ fontFamily: 'var(--font-mono)' }}>now</text>
        {/* dots */}
        {visible.map((p) => {
          const x = padL + ((+p.t - tStart) / ms) * (800 - padL - padR);
          const laneIdx = sevLanes.indexOf(p.sev);
          if (laneIdx < 0) return null;
          const y = padT + ((laneIdx + 0.5) / sevLanes.length) * (height - padT - padB);
          return (
            <circle key={p.id} cx={x} cy={y} r={p.sev === 'CRITICAL' ? 6 : p.sev === 'ERROR' ? 5 : 4}
              fill={sevColor[p.sev]}
              stroke="var(--bg-elev-1)" strokeWidth="1.5"
              opacity={0.9}
              style={{ cursor: onDotClick ? 'pointer' : 'default' }}
              onClick={() => onDotClick?.(p)}>
              <title>{p.msg}</title>
            </circle>
          );
        })}
      </svg>
    </div>
  );
}

/* ────────────────────────────────────────────────────────────
   RaceChart — 두 라인 누적 비교 차트
   data: [{ d: dayIndex, strategy, baseline }]
   ──────────────────────────────────────────────────────────── */
function RaceChart({ trace, h = 80, sCol = 'var(--buy)', bCol = 'var(--text-3)', fullWidth = true }) {
  if (!trace || trace.length < 2) return null;
  const W = 240;
  const all = trace.flatMap(d => [d.s, d.b]);
  const min = Math.min(0, ...all);
  const max = Math.max(...all);
  const range = (max - min) || 1;
  const X = (i) => (i / (trace.length - 1)) * W;
  const Y = (v) => h - ((v - min) / range) * h;

  const sPath = trace.map((d, i) => (i === 0 ? `M${X(i).toFixed(2)},${Y(d.s).toFixed(2)}` : `L${X(i).toFixed(2)},${Y(d.s).toFixed(2)}`)).join(' ');
  const bPath = trace.map((d, i) => (i === 0 ? `M${X(i).toFixed(2)},${Y(d.b).toFixed(2)}` : `L${X(i).toFixed(2)},${Y(d.b).toFixed(2)}`)).join(' ');
  const zeroY = Y(0);

  const last = trace[trace.length - 1];
  return (
    <svg width={fullWidth ? '100%' : W} height={h} viewBox={`0 0 ${W} ${h}`} preserveAspectRatio="none" style={{ display: 'block' }}>
      {/* zero baseline */}
      <line x1="0" y1={zeroY} x2={W} y2={zeroY} stroke="var(--border-subtle)" strokeWidth="0.5" strokeDasharray="2 3" />
      <path d={bPath} fill="none" stroke={bCol} strokeWidth="1.5" strokeLinecap="round" strokeDasharray="3 3" vectorEffect="non-scaling-stroke" />
      <path d={sPath} fill="none" stroke={sCol} strokeWidth="2" strokeLinecap="round" vectorEffect="non-scaling-stroke" />
      {/* end markers */}
      <circle cx={X(trace.length - 1)} cy={Y(last.s)} r="3" fill={sCol} stroke="var(--bg-elev-1)" strokeWidth="1.5" />
      <circle cx={X(trace.length - 1)} cy={Y(last.b)} r="2.5" fill={bCol} stroke="var(--bg-elev-1)" strokeWidth="1.5" />
    </svg>
  );
}

/* ────────────────────────────────────────────────────────────
   ToolHistogram — 도구 호출 빈도 막대
   counts: { tool_name: number }
   ──────────────────────────────────────────────────────────── */
function ToolHistogram({ counts, max }) {
  const entries = Object.entries(counts).sort((a, b) => b[1] - a[1]);
  const m = max || Math.max(...entries.map(e => e[1]), 1);
  return (
    <div className="tool-histo">
      {entries.map(([k, v]) => (
        <div key={k} className="tool-histo-row">
          <span className="tool-histo-label mono">{k}</span>
          <div className="tool-histo-bar-wrap">
            <div className="tool-histo-bar" style={{ width: `${(v/m)*100}%` }} />
          </div>
          <span className="tool-histo-count mono tnum">{v}</span>
        </div>
      ))}
    </div>
  );
}

Object.assign(window, {
  ClockDial, Treemap, GoalRing, GoalLadder, DotTimeline, RaceChart, ToolHistogram,
});
