/**
 * IndicatorGaugePanel
 * 투자분析3 화면 우측 슬라이드 패널 — 보조지표 현재값을 스피도미터 게이지로 표시
 */
import React, { useState, useMemo } from 'react';
import {
  Box,
  Typography,
  IconButton,
  Tooltip,
  Divider,
  Chip,
} from '@mui/material';
import ChevronLeftIcon from '@mui/icons-material/ChevronLeft';
import ChevronRightIcon from '@mui/icons-material/ChevronRight';
import DashboardIcon from '@mui/icons-material/Dashboard';
import ScheduleIcon from '@mui/icons-material/Schedule';
import type { IndicatorData } from '../services/backtestApi';

// ─────────────────────────────────────────────────────────────────────────────
// Types
// ─────────────────────────────────────────────────────────────────────────────

interface GaugeZone {
  from: number;
  to: number;
  color: string;
  label: string;
}

interface GaugeConfig {
  id: string;
  label: string;
  key: string;
  min: number;
  max: number;
  zones: GaugeZone[];
  centerValue?: number;
  formatValue?: (v: number) => string;
  signal?: (v: number) => 'buy' | 'sell' | 'neutral';
  group: string;
}

/** REST/WS·프론트 계산 간 필드명 차이 — 게이지는 첫 번째로 읽히는 유효 숫자를 사용 */
const GAUGE_VALUE_KEYS: Record<string, readonly string[]> = {
  stochK: ['stochK', 'stochasticK'],
  newPsychological: ['newPsychological', 'newPsy'],
  investPsychological: ['investPsychological', 'investPsy'],
  macdHistogram: ['macdHistogram', 'histogram'],
};

function readGaugeNumeric(row: IndicatorData | Record<string, unknown> | null | undefined, fieldKey: string): number | undefined {
  if (row == null) return undefined;
  const keys = GAUGE_VALUE_KEYS[fieldKey] ?? [fieldKey];
  const obj = row as Record<string, unknown>;
  for (const k of keys) {
    const raw = obj[k];
    if (raw !== undefined && raw !== null && Number.isFinite(Number(raw))) return Number(raw);
  }
  return undefined;
}

// ─────────────────────────────────────────────────────────────────────────────
// SVG Gauge geometry (컴팩트 · 대시보드 실시간 게이지와 유사한 비율)
// ─────────────────────────────────────────────────────────────────────────────

const G = {
  cx: 58,
  cy: 52,
  r: 42,
  sw: 7,
  nl: 32,
  vbW: 116,
  vbH: 94,
} as const;

/** 퀀타일 구간 사이 시각적 간격 (0~1 스케일 비율) */
const QUINTILE_PAD = 0.014;

/** 그룹별 5단계 팔레트 (WebSocket 대시보드 게이지와 유사한 그라데이션) */
const THEME_PALETTES: Record<string, string[]> = {
  오실레이터: ['#4ade80', '#a3e635', '#fbbf24', '#fb923c', '#f87171'],
  추세: ['#fef08a', '#fbbf24', '#fb923c', '#f87171', '#b91c1c'],
  거래량: ['#38bdf8', '#7dd3fc', '#bae6fd', '#e0f2fe', '#f8fafc'],
  변동성: ['#fcd34d', '#fb923c', '#f97316', '#ef4444', '#991b1b'],
  이격도: ['#a78bfa', '#c4b5fd', '#ddd6fe', '#e9d5ff', '#f0abfc'],
};

const DEFAULT_PALETTE = THEME_PALETTES.오실레이터;

// ─────────────────────────────────────────────────────────────────────────────
// SVG helpers
// ─────────────────────────────────────────────────────────────────────────────

/** value → ratio [0,1] clamped */
const toRatio = (v: number, min: number, max: number) =>
  Math.max(0, Math.min(1, (v - min) / (max - min)));

/** ratio → math angle in degrees (180° at min-left, 0° at max-right) */
const ratioToMathDeg = (r: number) => 180 - r * 180;

/** math angle → SVG point on arc */
const mathDegToPoint = (deg: number, rad: number = G.r) => ({
  x: G.cx + rad * Math.cos((deg * Math.PI) / 180),
  y: G.cy - rad * Math.sin((deg * Math.PI) / 180),
});

/** SVG arc path string for a value range */
const arcPath = (fromVal: number, toVal: number, min: number, max: number, rad: number = G.r) => {
  const r1 = toRatio(fromVal, min, max);
  const r2 = toRatio(toVal, min, max);
  const a1 = ratioToMathDeg(r1);
  const a2 = ratioToMathDeg(r2);
  const p1 = mathDegToPoint(a1, rad);
  const p2 = mathDegToPoint(a2, rad);
  const span = Math.abs(a1 - a2);
  const largeArc = span > 180 ? 1 : 0;
  const sweep = 1;
  return `M ${p1.x.toFixed(2)} ${p1.y.toFixed(2)} A ${rad} ${rad} 0 ${largeArc} ${sweep} ${p2.x.toFixed(2)} ${p2.y.toFixed(2)}`;
};

/** needle tip for a value */
const needleEndpoint = (v: number, min: number, max: number) => {
  const ratio = toRatio(v, min, max);
  const svgRot = ratio * 180 - 90;
  const rad = (svgRot * Math.PI) / 180;
  return {
    x: G.cx + G.nl * Math.sin(rad),
    y: G.cy - G.nl * Math.cos(rad),
    svgRot,
  };
};

/** 삼각 바늘 polygon points "x1,y1 x2,y2 x3,y3" */
const needleTrianglePoints = (tipX: number, tipY: number, halfBase = 3.8) => {
  const vx = tipX - G.cx;
  const vy = tipY - G.cy;
  const len = Math.hypot(vx, vy) || 1;
  const ux = vx / len;
  const uy = vy / len;
  const px = -uy * halfBase;
  const py = ux * halfBase;
  const lx = G.cx + px;
  const ly = G.cy + py;
  const rx = G.cx - px;
  const ry = G.cy - py;
  return `${lx.toFixed(2)},${ly.toFixed(2)} ${tipX.toFixed(2)},${tipY.toFixed(2)} ${rx.toFixed(2)},${ry.toFixed(2)}`;
};

function formatTickValue(min: number, max: number, pct: number): string {
  const v = min + (max - min) * (pct / 100);
  if (!Number.isFinite(v)) return '';
  const span = Math.abs(max - min);
  if (span >= 10000 && Math.abs(v) >= 1000) return `${(v / 1000).toFixed(0)}k`;
  if (span <= 1 && span > 0) return v.toFixed(2);
  if (span <= 20) return v.toFixed(1);
  if (Number.isInteger(min) && Number.isInteger(max) && Math.abs(v) < 1000) return String(Math.round(v));
  return v.toFixed(0);
}

// ─────────────────────────────────────────────────────────────────────────────
// Gauge Configurations
// ─────────────────────────────────────────────────────────────────────────────

const GAUGE_CONFIGS: GaugeConfig[] = [
  // ── 오실레이터 ───────────────────────────────────────────────────────────
  {
    id: 'rsi', label: 'RSI', key: 'rsi', min: 0, max: 100, centerValue: 50,
    group: '오실레이터',
    zones: [
      { from: 0,  to: 30,  color: '#1565C0', label: '과매도' },
      { from: 30, to: 50,  color: '#42A5F5', label: '약세' },
      { from: 50, to: 70,  color: '#66BB6A', label: '강세' },
      { from: 70, to: 100, color: '#EF5350', label: '과매수' },
    ],
    formatValue: (v) => v.toFixed(1),
    signal: (v) => v < 30 ? 'buy' : v > 70 ? 'sell' : 'neutral',
  },
  {
    id: 'stoch', label: 'Stochastic %K', key: 'stochK', min: 0, max: 100, centerValue: 50,
    group: '오실레이터',
    zones: [
      { from: 0,  to: 20,  color: '#1565C0', label: '과매도' },
      { from: 20, to: 80,  color: '#26A69A', label: '중립' },
      { from: 80, to: 100, color: '#EF5350', label: '과매수' },
    ],
    formatValue: (v) => v.toFixed(1),
    signal: (v) => v < 20 ? 'buy' : v > 80 ? 'sell' : 'neutral',
  },
  {
    id: 'cci', label: 'CCI', key: 'cci', min: -200, max: 200, centerValue: 0,
    group: '오실레이터',
    zones: [
      { from: -200, to: -100, color: '#1565C0', label: '과매도' },
      { from: -100, to:   -0, color: '#42A5F5', label: '약세' },
      { from:    0, to:  100, color: '#66BB6A', label: '강세' },
      { from:  100, to:  200, color: '#EF5350', label: '과매수' },
    ],
    formatValue: (v) => v.toFixed(1),
    signal: (v) => v < -100 ? 'buy' : v > 100 ? 'sell' : 'neutral',
  },
  {
    id: 'williamsR', label: 'Williams %R', key: 'williamsR', min: -100, max: 0, centerValue: -50,
    group: '오실레이터',
    zones: [
      { from: -100, to: -80, color: '#1565C0', label: '과매도' },
      { from:  -80, to: -20, color: '#26A69A', label: '중립' },
      { from:  -20, to:   0, color: '#EF5350', label: '과매수' },
    ],
    formatValue: (v) => v.toFixed(1),
    signal: (v) => v < -80 ? 'buy' : v > -20 ? 'sell' : 'neutral',
  },
  {
    id: 'newPsy', label: '신심리도', key: 'newPsychological', min: 0, max: 100, centerValue: 50,
    group: '오실레이터',
    zones: [
      { from:  0, to: 25,  color: '#1565C0', label: '과매도' },
      { from: 25, to: 75,  color: '#26A69A', label: '중립' },
      { from: 75, to: 100, color: '#EF5350', label: '과매수' },
    ],
    formatValue: (v) => v.toFixed(1),
    signal: (v) => v < 25 ? 'buy' : v > 75 ? 'sell' : 'neutral',
  },
  {
    id: 'investPsy', label: '투자심리도', key: 'investPsychological', min: 0, max: 100, centerValue: 50,
    group: '오실레이터',
    zones: [
      { from:  0, to: 25,  color: '#1565C0', label: '과매도' },
      { from: 25, to: 75,  color: '#26A69A', label: '중립' },
      { from: 75, to: 100, color: '#EF5350', label: '과매수' },
    ],
    formatValue: (v) => v.toFixed(1),
    signal: (v) => v < 25 ? 'buy' : v > 75 ? 'sell' : 'neutral',
  },
  // ── 추세 ─────────────────────────────────────────────────────────────────
  {
    id: 'adx', label: 'ADX', key: 'adx', min: 0, max: 100, centerValue: 25,
    group: '추세',
    zones: [
      { from:  0, to: 25,  color: '#78909C', label: '약한추세' },
      { from: 25, to: 50,  color: '#66BB6A', label: '추세중' },
      { from: 50, to: 75,  color: '#FFA726', label: '강한추세' },
      { from: 75, to: 100, color: '#EF5350', label: '매우강함' },
    ],
    formatValue: (v) => v.toFixed(1),
    signal: (v) => v > 25 ? 'buy' : 'neutral',
  },
  {
    id: 'plusDI', label: '+DI', key: 'plusDI', min: 0, max: 60, centerValue: 25,
    group: '추세',
    zones: [
      { from:  0, to: 20, color: '#78909C', label: '약' },
      { from: 20, to: 40, color: '#66BB6A', label: '중' },
      { from: 40, to: 60, color: '#4CAF50', label: '강' },
    ],
    formatValue: (v) => v.toFixed(1),
    signal: (v) => v > 25 ? 'buy' : 'neutral',
  },
  {
    id: 'minusDI', label: '-DI', key: 'minusDI', min: 0, max: 60, centerValue: 25,
    group: '추세',
    zones: [
      { from:  0, to: 20, color: '#78909C', label: '약' },
      { from: 20, to: 40, color: '#EF9A9A', label: '중' },
      { from: 40, to: 60, color: '#EF5350', label: '강' },
    ],
    formatValue: (v) => v.toFixed(1),
    signal: (v) => v > 25 ? 'sell' : 'neutral',
  },
  {
    // MACD 히스토그램은 종가 스케일에 비례해 절댓값이 매우 클 수 있음(고가 종목). 좁은 ±500은 의미 구간만 왜곡함.
    id: 'macd', label: 'MACD Hist', key: 'macdHistogram', min: -250000, max: 250000, centerValue: 0,
    group: '추세',
    zones: [
      { from: -250000, to: -100000, color: '#F44336', label: '강하락' },
      { from: -100000, to:       0, color: '#EF9A9A', label: '하락' },
      { from:       0, to:  100000, color: '#A5D6A7', label: '상승' },
      { from:  100000, to:  250000, color: '#4CAF50', label: '강상승' },
    ],
    formatValue: (v) => {
      if (Math.abs(v) >= 100000) return `${(v / 1000).toFixed(1)}k`;
      if (Math.abs(v) >= 10000) return `${(v / 1000).toFixed(2)}k`;
      return v.toFixed(2);
    },
    signal: (v) => v < -25000 ? 'sell' : v > 25000 ? 'buy' : 'neutral',
  },
  {
    id: 'momentum', label: 'Momentum', key: 'momentum', min: -10000, max: 10000, centerValue: 0,
    group: '추세',
    zones: [
      { from: -10000, to: -3000, color: '#F44336', label: '강하락' },
      { from:  -3000, to:     0, color: '#EF9A9A', label: '하락' },
      { from:      0, to:  3000, color: '#A5D6A7', label: '상승' },
      { from:   3000, to: 10000, color: '#4CAF50', label: '강상승' },
    ],
    formatValue: (v) => {
      if (Math.abs(v) >= 1000) return `${(v / 1000).toFixed(1)}k`;
      return v.toFixed(0);
    },
    signal: (v) => v < 0 ? 'sell' : 'buy',
  },
  {
    // lightweight-charts-indicators TRIX는 ROC에 1e4를 곱한 값을 plot0으로 반환함(차트와 동일 스케일).
    id: 'trix', label: 'TRIX', key: 'trix', min: -15, max: 15, centerValue: 0,
    group: '추세',
    zones: [
      { from: -15, to: -2, color: '#F44336', label: '하락' },
      { from:  -2, to:  2, color: '#9E9E9E', label: '중립' },
      { from:   2, to: 15, color: '#4CAF50', label: '상승' },
    ],
    formatValue: (v) => v.toFixed(4),
    signal: (v) => v < -2 ? 'sell' : v > 2 ? 'buy' : 'neutral',
  },
  // ── 거래량 ───────────────────────────────────────────────────────────────
  {
    id: 'vr', label: 'VR (거래량비율)', key: 'vr', min: 0, max: 500, centerValue: 100,
    group: '거래량',
    zones: [
      { from:   0, to:  70, color: '#1565C0', label: '과매도' },
      { from:  70, to: 150, color: '#26A69A', label: '중립' },
      { from: 150, to: 300, color: '#FFA726', label: '과매수' },
      { from: 300, to: 500, color: '#EF5350', label: '극과매수' },
    ],
    formatValue: (v) => v.toFixed(1),
    signal: (v) => v < 70 ? 'buy' : v > 300 ? 'sell' : 'neutral',
  },
  {
    id: 'volumeOsc', label: 'Volume OSC', key: 'volumeOsc', min: -100, max: 100, centerValue: 0,
    group: '거래량',
    zones: [
      { from: -100, to: -30, color: '#F44336', label: '거래감소' },
      { from:  -30, to:  30, color: '#9E9E9E', label: '중립' },
      { from:   30, to: 100, color: '#4CAF50', label: '거래증가' },
    ],
    formatValue: (v) => v.toFixed(2),
    signal: (v) => v < -30 ? 'sell' : v > 30 ? 'buy' : 'neutral',
  },
  // ── 변동성 ───────────────────────────────────────────────────────────────
  {
    id: 'bwi', label: 'BWI (밴드폭)', key: 'bwi', min: 0, max: 30, centerValue: 5,
    group: '변동성',
    zones: [
      { from:  0, to:  3,  color: '#1565C0', label: '수렴' },
      { from:  3, to: 10,  color: '#26A69A', label: '보통' },
      { from: 10, to: 18,  color: '#FFA726', label: '확장' },
      { from: 18, to: 30,  color: '#EF5350', label: '강확장' },
    ],
    formatValue: (v) => v.toFixed(2),
    signal: (v) => v < 3 ? 'neutral' : v > 15 ? 'sell' : 'neutral',
  },
  // ── 이격도 ───────────────────────────────────────────────────────────────
  {
    id: 'disparityUS', label: '이격도(초단기)', key: 'disparityUltraShort', min: -10, max: 10, centerValue: 0,
    group: '이격도',
    zones: [
      { from: -10, to: -3,  color: '#1565C0', label: '과매도' },
      { from:  -3, to:  3,  color: '#26A69A', label: '정상' },
      { from:   3, to: 10,  color: '#EF5350', label: '과열' },
    ],
    formatValue: (v) => `${v.toFixed(2)}%`,
    signal: (v) => v < -3 ? 'buy' : v > 3 ? 'sell' : 'neutral',
  },
  {
    id: 'disparityS', label: '이격도(단기)', key: 'disparityShort', min: -10, max: 10, centerValue: 0,
    group: '이격도',
    zones: [
      { from: -10, to: -3,  color: '#1565C0', label: '과매도' },
      { from:  -3, to:  3,  color: '#26A69A', label: '정상' },
      { from:   3, to: 10,  color: '#EF5350', label: '과열' },
    ],
    formatValue: (v) => `${v.toFixed(2)}%`,
    signal: (v) => v < -3 ? 'buy' : v > 3 ? 'sell' : 'neutral',
  },
];

// Group labels
const GROUP_COLORS: Record<string, string> = {
  '오실레이터': '#42A5F5',
  '추세': '#FFA726',
  '거래량': '#66BB6A',
  '변동성': '#EF5350',
  '이격도': '#AB47BC',
};

// ─────────────────────────────────────────────────────────────────────────────
// GaugeWidget component
// ─────────────────────────────────────────────────────────────────────────────

interface GaugeWidgetProps {
  config: GaugeConfig;
  value: number | undefined;
  isDark: boolean;
}

/** 값이 zones 정의 밖이면, 음수 초과 → 첫 구간·양수 초과 → 마지막 구간 (MACD 등 대용량 음수 오표시 방지) */
function resolveCurrentZone(zones: GaugeZone[], v: number, min: number, max: number): GaugeZone | null {
  if (!zones.length) return null;
  if (v < min) return zones[0];
  if (v > max) return zones[zones.length - 1];
  return zones.find(z => v >= z.from && v <= z.to) ?? zones[Math.floor(zones.length / 2)];
}

const GaugeWidget: React.FC<GaugeWidgetProps> = ({ config, value, isDark }) => {
  const { min, max, zones, label, formatValue, signal, group } = config;

  const safeVal = value !== undefined && Number.isFinite(value) ? value : undefined;
  const clampedVal = safeVal !== undefined ? Math.max(min, Math.min(max, safeVal)) : undefined;
  const outOfRange = safeVal !== undefined && (safeVal < min || safeVal > max);

  const palette = THEME_PALETTES[group] ?? DEFAULT_PALETTE;
  const accent = GROUP_COLORS[group] ?? palette[2];

  const needleTip = clampedVal !== undefined ? needleEndpoint(clampedVal, min, max) : null;

  const currentZone = safeVal !== undefined ? resolveCurrentZone(zones, safeVal, min, max) : null;

  const signalResult = safeVal !== undefined && signal ? signal(safeVal) : undefined;
  const signalColor = signalResult === 'buy'
    ? '#4ade80'
    : signalResult === 'sell'
    ? '#f87171'
    : isDark ? 'rgba(226,232,240,0.65)' : '#64748b';

  const valueDisplayColor = isDark ? '#f8fafc' : '#0f172a';

  const textColor = isDark ? '#f1f5f9' : '#0f172a';
  const subText = isDark ? 'rgba(148,163,184,0.95)' : '#64748b';
  const needleFill = '#ffffff';
  const trackBg = isDark ? 'rgba(30,41,59,0.85)' : 'rgba(226,232,240,0.95)';

  const displayVal = safeVal !== undefined
    ? (formatValue ? formatValue(safeVal) : safeVal.toFixed(2))
    : '—';

  const tickPcts = [0, 20, 40, 60, 80, 100] as const;

  const cardBg = isDark ? 'rgba(15,23,42,0.75)' : 'rgba(255,255,255,0.92)';
  const pillMuted = isDark ? 'rgba(51,65,85,0.95)' : 'rgba(241,245,249,0.98)';

  const signalPillText = signalResult === 'buy' ? '매수' : signalResult === 'sell' ? '매도' : '중립';

  return (
    <Box
      sx={{
        bgcolor: cardBg,
        border: `1px solid ${isDark ? `${accent}55` : `${accent}40`}`,
        borderRadius: 2,
        boxShadow: isDark ? `0 0 14px ${accent}22` : `0 1px 6px ${accent}18`,
        overflow: 'hidden',
        display: 'flex',
        flexDirection: 'column',
        minWidth: 0,
      }}
    >
      <Typography
        sx={{
          fontSize: '0.62rem',
          fontWeight: 700,
          color: textColor,
          textAlign: 'center',
          pt: 0.45,
          px: 0.5,
          lineHeight: 1.2,
        }}
      >
        {label}
        {outOfRange && (
          <Typography component="span" sx={{ fontSize: '0.5rem', color: '#fb923c', ml: 0.25 }}>
            !
          </Typography>
        )}
      </Typography>

      <svg
        viewBox={`0 0 ${G.vbW} ${G.vbH}`}
        style={{ display: 'block', width: '100%', height: 'auto' }}
        xmlns="http://www.w3.org/2000/svg"
      >
        <defs>
          <filter id={`gglow-${config.id}`} x="-40%" y="-40%" width="180%" height="180%">
            <feGaussianBlur stdDeviation="1.6" result="b" />
            <feMerge>
              <feMergeNode in="b" />
              <feMergeNode in="SourceGraphic" />
            </feMerge>
          </filter>
        </defs>

        {/* 배경 트랙 */}
        <path
          d={arcPath(min, max, min, max)}
          fill="none"
          stroke={trackBg}
          strokeWidth={G.sw}
          strokeLinecap="round"
        />

        {/* 5등분 컬러 세그먼트 (간격 있음) */}
        {palette.map((color, i) => {
          const t0 = i / 5 + QUINTILE_PAD;
          const t1 = (i + 1) / 5 - QUINTILE_PAD;
          const fromV = min + (max - min) * t0;
          const toV = min + (max - min) * t1;
          return (
            <path
              key={i}
              d={arcPath(fromV, toV, min, max)}
              fill="none"
              stroke={color}
              strokeWidth={G.sw}
              strokeLinecap="round"
              opacity={0.92}
            />
          );
        })}

        {/* 눈금 */}
        {tickPcts.map((pct, i) => {
          const r2 = pct / 100;
          const deg = ratioToMathDeg(r2);
          const rad = (deg * Math.PI) / 180;
          const ri = G.r + G.sw / 2 + 0.5;
          const ro = G.r + G.sw / 2 + 4;
          const lx = G.cx + ri * Math.cos(rad);
          const ly = G.cy - ri * Math.sin(rad);
          const hx = G.cx + ro * Math.cos(rad);
          const hy = G.cy - ro * Math.sin(rad);
          const labelStr = formatTickValue(min, max, pct);
          const tx = G.cx + (ro + 7) * Math.cos(rad);
          const ty = G.cy - (ro + 7) * Math.sin(rad) + 2.5;
          return (
            <g key={i}>
              <line
                x1={lx.toFixed(2)}
                y1={ly.toFixed(2)}
                x2={hx.toFixed(2)}
                y2={hy.toFixed(2)}
                stroke={isDark ? 'rgba(148,163,184,0.55)' : 'rgba(100,116,139,0.55)'}
                strokeWidth={1}
              />
              <text
                x={tx.toFixed(1)}
                y={ty.toFixed(1)}
                textAnchor="middle"
                fill={subText}
                fontSize="5.5"
                fontWeight={600}
              >
                {labelStr}
              </text>
            </g>
          );
        })}

        {/* 중앙 값 (글로우 — 그룹 악센트) */}
        <text
          x={G.cx}
          y={G.cy - G.r * 0.28}
          textAnchor="middle"
          fill={accent}
          fontSize="11"
          fontWeight={800}
          filter={`url(#gglow-${config.id})`}
        >
          {displayVal}
        </text>
        <text
          x={G.cx}
          y={G.cy - G.r * 0.28}
          textAnchor="middle"
          fill={valueDisplayColor}
          fontSize="11"
          fontWeight={800}
        >
          {displayVal}
        </text>

        {/* 바늘 그림자 */}
        {needleTip && (
          <polygon
            points={needleTrianglePoints(needleTip.x, needleTip.y, 4.2)}
            fill="rgba(0,0,0,0.35)"
            transform="translate(0.6,0.8)"
          />
        )}
        {needleTip && (
          <polygon
            points={needleTrianglePoints(needleTip.x, needleTip.y, 3.6)}
            fill={needleFill}
            stroke="rgba(15,23,42,0.25)"
            strokeWidth={0.35}
          />
        )}
        <circle cx={G.cx} cy={G.cy} r={3.2} fill={isDark ? '#334155' : '#94a3b8'} />
        <circle cx={G.cx} cy={G.cy} r={1.6} fill={isDark ? '#e2e8f0' : '#1e293b'} />

        {currentZone && (
          <text x={G.cx} y={G.cy + 14} textAnchor="middle" fill={subText} fontSize="5.8" fontWeight={600}>
            {currentZone.label}
          </text>
        )}
      </svg>

      {/* 하단 필 배지 */}
      <Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', gap: 0.4, px: 0.5, pb: 0.45, pt: 0.1 }}>
        <Box
          sx={{
            flex: 1,
            minWidth: 0,
            py: 0.2,
            px: 0.45,
            borderRadius: 10,
            bgcolor: `${palette[0]}33`,
            border: `1px solid ${palette[0]}44`,
          }}
        >
          <Typography sx={{ fontSize: '0.52rem', fontWeight: 800, color: textColor, lineHeight: 1.2, textAlign: 'center' }}>
            {displayVal}
          </Typography>
        </Box>
        <Box
          sx={{
            display: 'flex',
            alignItems: 'center',
            gap: 0.15,
            py: 0.2,
            px: 0.4,
            borderRadius: 10,
            bgcolor: pillMuted,
            border: isDark ? '1px solid rgba(71,85,105,0.6)' : '1px solid rgba(203,213,225,0.9)',
            flexShrink: 0,
          }}
        >
          <ScheduleIcon sx={{ fontSize: 11, color: subText }} />
          <Typography sx={{ fontSize: '0.52rem', fontWeight: 700, color: signalColor, lineHeight: 1 }}>
            {signalPillText}
          </Typography>
        </Box>
      </Box>
    </Box>
  );
};

// ─────────────────────────────────────────────────────────────────────────────
// Signal Summary Badge
// ─────────────────────────────────────────────────────────────────────────────

interface SummaryProps {
  indicatorData: IndicatorData | null | undefined;
  isDark: boolean;
}

const SignalSummary: React.FC<SummaryProps> = ({ indicatorData, isDark }) => {
  const signals = useMemo(() => {
    if (!indicatorData) return { buy: 0, sell: 0, neutral: 0 };
    let buy = 0, sell = 0, neutral = 0;
    GAUGE_CONFIGS.forEach(cfg => {
      if (!cfg.signal) return;
      const v = readGaugeNumeric(indicatorData, cfg.key);
      if (v === undefined) return;
      const s = cfg.signal(v);
      if (s === 'buy') buy++;
      else if (s === 'sell') sell++;
      else neutral++;
    });
    return { buy, sell, neutral };
  }, [indicatorData]);

  const total = signals.buy + signals.sell + signals.neutral;
  const bullPct = total > 0 ? Math.round((signals.buy / total) * 100) : 0;
  const bearPct = total > 0 ? Math.round((signals.sell / total) * 100) : 0;
  const overall = signals.buy > signals.sell + signals.neutral ? '강한매수'
    : signals.sell > signals.buy + signals.neutral ? '강한매도'
    : signals.buy > signals.sell ? '매수우세'
    : signals.sell > signals.buy ? '매도우세' : '중립';
  const overallColor = overall.includes('매수') ? '#4ade80'
    : overall.includes('매도') ? '#f87171' : '#94a3b8';

  return (
    <Box
      sx={{
        bgcolor: isDark ? 'rgba(15,23,42,0.55)' : 'rgba(248,250,252,0.98)',
        border: `1px solid ${isDark ? 'rgba(71,85,105,0.55)' : '#e2e8f0'}`,
        borderRadius: 1.5,
        p: 0.65,
        mb: 0.75,
      }}
    >
      <Typography sx={{ fontSize: '0.58rem', color: isDark ? '#94a3b8' : '#64748b', mb: 0.35 }}>
        종합 신호 분석
      </Typography>
      <Typography sx={{ fontSize: '0.78rem', fontWeight: 800, color: overallColor, lineHeight: 1.2, mb: 0.4 }}>
        {overall}
      </Typography>
      <Box sx={{ display: 'flex', height: 5, borderRadius: 2.5, overflow: 'hidden', mb: 0.45 }}>
        <Box sx={{ width: `${bullPct}%`, bgcolor: '#4ade80', transition: 'width 0.4s' }} />
        <Box sx={{ width: `${bearPct}%`, bgcolor: '#f87171', transition: 'width 0.4s' }} />
        <Box sx={{ flex: 1, bgcolor: isDark ? 'rgba(51,65,85,0.6)' : '#e2e8f0' }} />
      </Box>
      <Box sx={{ display: 'flex', flexWrap: 'wrap', gap: 0.35 }}>
        <Chip label={`매수 ${signals.buy}`} size="small" sx={{ fontSize: '0.52rem', height: 16, bgcolor: 'rgba(74,222,128,0.15)', color: '#4ade80' }} />
        <Chip label={`매도 ${signals.sell}`} size="small" sx={{ fontSize: '0.52rem', height: 16, bgcolor: 'rgba(248,113,113,0.15)', color: '#f87171' }} />
        <Chip label={`중립 ${signals.neutral}`} size="small" sx={{ fontSize: '0.52rem', height: 16, bgcolor: isDark ? 'rgba(51,65,85,0.5)' : '#f1f5f9', color: isDark ? '#94a3b8' : '#64748b' }} />
      </Box>
    </Box>
  );
};

// ─────────────────────────────────────────────────────────────────────────────
// IndicatorGaugePanel (main export)
// ─────────────────────────────────────────────────────────────────────────────

export interface IndicatorGaugePanelProps {
  open: boolean;
  onToggle: () => void;
  indicatorData: IndicatorData | null | undefined;
  isDark: boolean;
}

const PANEL_WIDTH = 292;

const IndicatorGaugePanel: React.FC<IndicatorGaugePanelProps> = ({
  open,
  onToggle,
  indicatorData,
  isDark,
}) => {
  const [openGroups, setOpenGroups] = useState<Record<string, boolean>>({
    '오실레이터': true,
    '추세': true,
    '거래량': true,
    '변동성': true,
    '이격도': false,
  });

  const toggleGroup = (group: string) =>
    setOpenGroups(prev => ({ ...prev, [group]: !prev[group] }));

  const groups = useMemo(() => {
    const map: Record<string, GaugeConfig[]> = {};
    GAUGE_CONFIGS.forEach(cfg => {
      if (!map[cfg.group]) map[cfg.group] = [];
      map[cfg.group].push(cfg);
    });
    return map;
  }, []);

  const panelBg = isDark ? '#121218' : '#f5f5f5';
  const headerBg = isDark ? '#1a1a28' : '#e8eaf6';
  const borderColor = isDark ? '#2a2a3a' : '#c5cae9';

  return (
    <>
      {/* Toggle button (always visible) */}
      <Tooltip title={open ? '게이지 패널 숨기기' : '지표 게이지 패널 열기'} placement="left">
        <IconButton
          onClick={onToggle}
          size="small"
          sx={{
            position: 'fixed',
            right: open ? PANEL_WIDTH : 0,
            top: '50%',
            transform: 'translateY(-50%)',
            zIndex: 1300,
            bgcolor: isDark ? '#1e1e3a' : '#3949ab',
            color: '#fff',
            borderRadius: '6px 0 0 6px',
            width: 22,
            height: 56,
            '&:hover': { bgcolor: isDark ? '#2a2a5a' : '#1a237e' },
            transition: 'right 0.3s ease-in-out',
            boxShadow: '-2px 0 8px rgba(0,0,0,0.3)',
          }}
        >
          {open ? <ChevronRightIcon fontSize="small" /> : <ChevronLeftIcon fontSize="small" />}
        </IconButton>
      </Tooltip>

      {/* Sliding panel */}
      <Box
        sx={{
          position: 'fixed',
          top: 0,
          right: open ? 0 : -PANEL_WIDTH,
          bottom: 0,
          width: PANEL_WIDTH,
          zIndex: 1200,
          bgcolor: panelBg,
          borderLeft: `1px solid ${borderColor}`,
          display: 'flex',
          flexDirection: 'column',
          transition: 'right 0.3s ease-in-out',
          boxShadow: open ? '-4px 0 16px rgba(0,0,0,0.25)' : 'none',
        }}
      >
        {/* Panel header */}
        <Box
          sx={{
            px: 1.5, py: 1,
            bgcolor: headerBg,
            borderBottom: `1px solid ${borderColor}`,
            display: 'flex',
            alignItems: 'center',
            gap: 0.75,
            flexShrink: 0,
          }}
        >
          <DashboardIcon sx={{ fontSize: 16, color: '#5c6bc0' }} />
          <Typography sx={{ fontSize: '0.78rem', fontWeight: 700, color: isDark ? '#ccc' : '#333', flex: 1 }}>
            지표 게이지 대시보드
          </Typography>
        </Box>

        {/* Scrollable content */}
        <Box sx={{ flex: 1, overflowY: 'auto', overflowX: 'hidden', px: 1, py: 1 }}>
          {/* Signal summary */}
          <SignalSummary indicatorData={indicatorData} isDark={isDark} />

          {/* Grouped gauges */}
          {Object.entries(groups).map(([groupName, configs]) => (
            <Box key={groupName} sx={{ mb: 0.5 }}>
              {/* Group header */}
              <Box
                onClick={() => toggleGroup(groupName)}
                sx={{
                  display: 'flex',
                  alignItems: 'center',
                  gap: 0.5,
                  cursor: 'pointer',
                  py: 0.5,
                  mb: 0.5,
                  userSelect: 'none',
                }}
              >
                <Box
                  sx={{
                    width: 8, height: 8, borderRadius: '50%',
                    bgcolor: GROUP_COLORS[groupName] || '#666',
                    flexShrink: 0,
                  }}
                />
                <Typography sx={{ fontSize: '0.7rem', fontWeight: 700, color: GROUP_COLORS[groupName] || '#aaa', flex: 1 }}>
                  {groupName}
                </Typography>
                <Typography sx={{ fontSize: '0.65rem', color: isDark ? '#888' : '#aaa' }}>
                  {openGroups[groupName] ? '▲' : '▼'}
                </Typography>
              </Box>
              <Divider sx={{ mb: 0.75, borderColor: isDark ? '#333' : '#ddd' }} />

              {/* Gauges — 2열 그리드 */}
              {openGroups[groupName] && (
                <Box
                  sx={{
                    display: 'grid',
                    gridTemplateColumns: '1fr 1fr',
                    gap: 0.65,
                    alignItems: 'stretch',
                  }}
                >
                  {configs.map(cfg => (
                    <GaugeWidget
                      key={cfg.id}
                      config={cfg}
                      value={
                        indicatorData !== null && indicatorData !== undefined
                          ? readGaugeNumeric(indicatorData, cfg.key)
                          : undefined
                      }
                      isDark={isDark}
                    />
                  ))}
                </Box>
              )}
            </Box>
          ))}
        </Box>

        {/* Footer */}
        <Box
          sx={{
            px: 1.5, py: 0.75,
            bgcolor: headerBg,
            borderTop: `1px solid ${borderColor}`,
            flexShrink: 0,
          }}
        >
          <Typography sx={{ fontSize: '0.6rem', color: isDark ? '#666' : '#999', textAlign: 'center' }}>
            차트 마지막 봉 지표와 동일 · 실시간 반영
          </Typography>
        </Box>
      </Box>
    </>
  );
};

export default IndicatorGaugePanel;
