import React, { useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState } from 'react';
import { createPortal } from 'react-dom';
import Draggable from 'react-draggable';
import { Box, IconButton, Paper, Typography } from '@mui/material';
import CloseIcon from '@mui/icons-material/Close';
import DragIndicatorIcon from '@mui/icons-material/DragIndicator';
import FullscreenIcon from '@mui/icons-material/Fullscreen';
import FullscreenExitIcon from '@mui/icons-material/FullscreenExit';
import type { CandleData, IndicatorData } from '../services/backtestApi';
import type { MALineConfig, IndicatorVisibilitySettings } from '../contexts/IndicatorVisibilityContext';

/** CandlestickChart와 동일한 SMA 계산 */
function calculateSMA(
  data: { time: number; close: number }[],
  period: number
): { time: number; value: number | null }[] {
  const result: { time: number; value: number | null }[] = [];
  for (let i = 0; i < data.length; i++) {
    if (i < period - 1) {
      result.push({ time: data[i].time, value: null });
    } else {
      let sum = 0;
      for (let j = 0; j < period; j++) sum += data[i - j].close;
      result.push({ time: data[i].time, value: sum / period });
    }
  }
  return result;
}

function candleTimeToSec(t: string | number | undefined): number {
  if (t == null) return NaN;
  if (typeof t === 'number') return t > 1e12 ? Math.floor(t / 1000) : Math.floor(t);
  return Math.floor(new Date(t).getTime() / 1000);
}

/**
 * 형광 플래시 비교 전용 — 화면 문자열과 분리
 * - time: ISO 문자열 ↔ epoch(ms/초) 혼용 시에도 동일 봉이면 같은 키
 * - 숫자: `formatNum`의 `toLocaleString(undefined)`는 렌더·환경에 따라 미세히 달라질 수 있어 오탐 방지
 */
function flashCompareTimeKey(t: string | number | undefined): string {
  const s = candleTimeToSec(t);
  return Number.isFinite(s) ? `u:${s}` : `raw:${String(t ?? '')}`;
}

function flashCompareNumKey(v: unknown): string {
  if (v === null || v === undefined || v === '') return '—';
  const n = Number(v);
  if (!Number.isFinite(n)) return '—';
  return n.toLocaleString('en-US', {
    useGrouping: false,
    maximumFractionDigits: 12,
    minimumFractionDigits: 0,
  });
}

function formatNum(v: unknown): string {
  if (v === null || v === undefined || v === '') return '—';
  const n = Number(v);
  if (!Number.isFinite(n)) return '—';
  const abs = Math.abs(n);
  if (abs >= 1000) return n.toLocaleString(undefined, { maximumFractionDigits: 2 });
  if (abs >= 1) return n.toLocaleString(undefined, { maximumFractionDigits: 4 });
  if (abs >= 0.0001) return n.toLocaleString(undefined, { maximumFractionDigits: 6 });
  return n.toExponential(3);
}

function buildSortedDataForMA(
  allIndicatorData: IndicatorData[],
  maCalculationData: CandleData[],
  candleData: CandleData[]
): { time: number; close: number }[] {
  const indicatorForCalculation =
    allIndicatorData?.length > 0 ? allIndicatorData : maCalculationData?.length > 0 ? maCalculationData : candleData;
  const dataForCalculation = maCalculationData?.length > 0 ? maCalculationData : candleData;
  if (!indicatorForCalculation?.length || !dataForCalculation?.length) return [];
  return (indicatorForCalculation as any[])
    .map((d: any, i: number) => {
      if (d == null || typeof d !== 'object') return null;
      const fallbackCandle = dataForCalculation[i];
      const close =
        d.close !== undefined && d.close !== null && !Number.isNaN(Number(d.close))
          ? Number(d.close)
          : fallbackCandle != null &&
              fallbackCandle.close != null &&
              !Number.isNaN(Number(fallbackCandle.close))
            ? Number(fallbackCandle.close)
            : null;
      if (close === null || !Number.isFinite(close)) return null;
      const timeValue = d.timestamp || d.time || d.date || fallbackCandle?.time;
      if (timeValue == null && fallbackCandle?.time == null) return null;
      const tv = timeValue ?? fallbackCandle?.time;
      const tSec = Math.floor(typeof tv === 'string' ? new Date(tv).getTime() / 1000 : Number(tv));
      if (!Number.isFinite(tSec)) return null;
      return { time: tSec, close };
    })
    .filter((x): x is { time: number; close: number } => x != null);
}

function pickDisplayIndicatorRow(
  candleData: CandleData[],
  indicatorData: IndicatorData[]
): IndicatorData | null {
  if (!candleData.length) return null;
  const last = candleData[candleData.length - 1];
  const t = last.time;
  const byTime = indicatorData.find((r) => r.time === t || r.timestamp === t);
  if (byTime) return byTime;
  if (indicatorData.length >= candleData.length) return indicatorData[candleData.length - 1];
  return indicatorData.length ? indicatorData[indicatorData.length - 1] : null;
}

export interface IchimokuFlags {
  tenkan: boolean;
  kijun: boolean;
  chikou: boolean;
  senkouA: boolean;
  senkouB: boolean;
}

export interface RealtimeChartValuesPopupProps {
  /** null이면 닫힘. 열릴 때 버튼 위치 기준으로 패널 초기 좌표를 잡음 */
  anchorEl: HTMLElement | null;
  onClose: () => void;
  isDark: boolean;
  symbol: string;
  timeInterval: string;
  candleData: CandleData[];
  maCalculationData: CandleData[];
  allIndicatorData: IndicatorData[];
  indicatorData: IndicatorData[];
  maLines: MALineConfig[];
  indicatorVisibility: IndicatorVisibilitySettings;
  ichimokuSettings: IchimokuFlags;
  subPanelsOrder: string[];
  realtimePrice: number | null;
  /** false면 해당 종목 실시간 스트림이 아님 — 형광 연두 플래시를 쓰지 않고 즉시 원래 색으로 표시 */
  realtimeReceiving?: boolean;
}

/** 형광 연두 (실시간 갱신 플래시) */
const FLASH_LIME = '#CCFF00';

function Row({
  label,
  value,
  flash,
}: {
  label: string;
  value: string;
  flash?: boolean;
}) {
  const labelSx = flash
    ? {
        color: FLASH_LIME,
        fontWeight: 700,
        textShadow: '0 0 8px rgba(204,255,0,0.55)',
        flexShrink: 0,
      }
    : { flexShrink: 0 };
  const valueSx = flash
    ? {
        color: FLASH_LIME,
        fontWeight: 700,
        fontFamily: 'monospace',
        textAlign: 'right' as const,
        wordBreak: 'break-all' as const,
        textShadow: '0 0 8px rgba(204,255,0,0.45)',
      }
    : {
        fontFamily: 'monospace',
        textAlign: 'right' as const,
        wordBreak: 'break-all' as const,
      };

  return (
    <Box sx={{ display: 'flex', justifyContent: 'space-between', gap: 1, py: 0.25, fontSize: '0.75rem' }}>
      <Typography component="span" color={flash ? undefined : 'text.secondary'} sx={labelSx}>
        {label}
      </Typography>
      <Typography component="span" sx={valueSx}>
        {value}
      </Typography>
    </Box>
  );
}

function sectionTitle(text: string) {
  return (
    <Typography variant="caption" color="text.secondary" sx={{ fontWeight: 700, display: 'block', mt: 1, mb: 0.5 }}>
      {text}
    </Typography>
  );
}

function subPanelRows(type: string, r: IndicatorData): { label: string; value: string }[] {
  switch (type) {
    case 'volume':
      return [{ label: '거래량', value: formatNum(r.volume) }];
    case 'macd':
      return [
        { label: 'MACD', value: formatNum(r.macdLine ?? r.macd) },
        { label: 'Signal', value: formatNum(r.signalLine ?? r.signal ?? r.macdSignal) },
        { label: 'Histogram', value: formatNum(r.macdHistogram ?? r.histogram) },
      ];
    case 'rsi':
      return [{ label: 'RSI', value: formatNum(r.rsi) }];
    case 'stochastic':
      return [
        { label: '%K', value: formatNum(r.stochK) },
        { label: '%D', value: formatNum(r.stochD) },
      ];
    case 'cci':
      return [{ label: 'CCI', value: formatNum(r.cci) }];
    case 'adx':
      return [
        { label: 'ADX', value: formatNum(r.adx) },
        { label: '+DI', value: formatNum(r.plusDI) },
        { label: '-DI', value: formatNum(r.minusDI) },
      ];
    case 'obv':
      return [
        { label: 'OBV', value: formatNum(r.obv) },
        { label: 'OBV Signal', value: formatNum(r.obvSignal) },
      ];
    case 'newPsychological':
      return [{ label: '신심리도', value: formatNum(r.newPsychological ?? r.newPsy) }];
    case 'investPsychological':
      return [{ label: '투자심리도', value: formatNum(r.investPsychological ?? r.investPsy) }];
    case 'bwi':
      return [{ label: 'BWI', value: formatNum(r.bwi) }];
    case 'dmi':
      return [
        { label: '+DI', value: formatNum(r.plusDI) },
        { label: '-DI', value: formatNum(r.minusDI) },
      ];
    case 'williamsR':
      return [{ label: 'Williams %R', value: formatNum(r.williamsR) }];
    case 'trix':
      return [
        { label: 'TRIX', value: formatNum(r.trix) },
        { label: 'TRIX Signal', value: formatNum(r.trixSignal) },
      ];
    case 'volumeOsc':
      return [{ label: 'Volume OSC', value: formatNum(r.volumeOsc) }];
    case 'vr':
      return [
        { label: 'VR', value: formatNum(r.vr) },
        { label: 'VR2', value: formatNum(r.vr2) },
      ];
    case 'disparity':
      return [{ label: '이격도', value: formatNum(r.disparity) }];
    default:
      return [];
  }
}

function panelEnabled(type: string, v: IndicatorVisibilitySettings): boolean {
  switch (type) {
    case 'volume':
      return v.volume;
    case 'macd':
      return v.macdLine;
    case 'rsi':
      return v.rsi;
    case 'stochastic':
      return v.stochasticK || v.stochasticD || v.stochasticSlow;
    case 'cci':
      return v.cci;
    case 'adx':
      return v.adx;
    case 'obv':
      return v.obv;
    case 'newPsychological':
      return v.newPsychological;
    case 'investPsychological':
      return v.investPsychological;
    case 'bwi':
      return v.bwi;
    case 'dmi':
      return v.plusDi || v.minusDi;
    case 'williamsR':
      return v.williamsR;
    case 'trix':
      return v.trix;
    case 'volumeOsc':
      return v.volumeOsc;
    case 'vr':
      return v.vr;
    case 'disparity':
      return v.disparity;
    default:
      return false;
  }
}

type ValuesSnapshot = {
  lastCandle: CandleData;
  row: IndicatorData | null;
  maValues: { id: string; period: number; value: string }[];
} | null;

/** 팝업 행과 동일한 표시 문자열 맵 (값 변경 플래시 비교용) */
function buildFlatValuesForFlash(
  snapshot: ValuesSnapshot,
  realtimePrice: number | null,
  indicatorVisibility: IndicatorVisibilitySettings,
  ichimokuSettings: IchimokuFlags | undefined,
  subPanelsOrder: string[],
): Record<string, string> {
  const flat: Record<string, string> = {};
  if (!snapshot?.lastCandle) return flat;
  const { lastCandle: last, row, maValues } = snapshot;
  flat['candle:time'] = flashCompareTimeKey(last.time);
  flat['candle:open'] = flashCompareNumKey(last.open);
  flat['candle:high'] = flashCompareNumKey(last.high);
  flat['candle:low'] = flashCompareNumKey(last.low);
  flat['candle:close'] = flashCompareNumKey(last.close);
  flat['candle:volume'] = flashCompareNumKey(last.volume);
  if (realtimePrice != null && Number.isFinite(realtimePrice)) {
    flat['candle:rt'] = flashCompareNumKey(realtimePrice);
  }
  maValues.forEach((m) => {
    flat[`ma:${m.id}`] = m.value;
  });
  if (row && ichimokuSettings) {
    if (ichimokuSettings.tenkan) flat['ichimoku:tenkan'] = formatNum(row.ichimokuTenkan);
    if (ichimokuSettings.kijun) flat['ichimoku:kijun'] = formatNum(row.ichimokuKijun);
    if (ichimokuSettings.chikou) flat['ichimoku:chikou'] = formatNum(row.ichimokuChikou);
    if (ichimokuSettings.senkouA) flat['ichimoku:senkouA'] = formatNum(row.ichimokuSenkouA);
    if (ichimokuSettings.senkouB) flat['ichimoku:senkouB'] = formatNum(row.ichimokuSenkouB);
  }
  if (row) {
    if (indicatorVisibility.bollingerUpper) flat['bb:upper'] = formatNum(row.bollingerUpper);
    if (indicatorVisibility.bollingerMiddle) flat['bb:middle'] = formatNum(row.bollingerMiddle);
    if (indicatorVisibility.bollingerLower) flat['bb:lower'] = formatNum(row.bollingerLower);
  }
  if (row) {
    subPanelsOrder.forEach((type) => {
      if (!panelEnabled(type, indicatorVisibility)) return;
      subPanelRows(type, row).forEach(({ label, value }) => {
        flat[`sub:${type}:${label}`] = value;
      });
    });
  }
  return flat;
}

const FLASH_HIGHLIGHT_MS = 650;
const DEFAULT_PANEL_WIDTH = 360;
const DEFAULT_PANEL_HEIGHT = 480;
const MIN_PANEL_WIDTH = 280;
const MIN_PANEL_HEIGHT = 240;
const MAXIMIZE_INSET = 8;

function clampPanelPosition(x: number, y: number, panelWidth: number, panelHeight: number): { x: number; y: number } {
  if (typeof window === 'undefined') return { x, y };
  const margin = 8;
  const maxX = Math.max(margin, window.innerWidth - panelWidth - margin);
  const maxY = Math.max(margin, window.innerHeight - panelHeight - margin);
  return {
    x: Math.min(Math.max(margin, x), maxX),
    y: Math.min(Math.max(margin, y), maxY),
  };
}

function positionNearAnchor(anchor: HTMLElement, panelWidth: number, panelHeight: number): { x: number; y: number } {
  const r = anchor.getBoundingClientRect();
  const margin = 8;
  const x = r.right - panelWidth;
  const y = r.bottom + margin;
  return clampPanelPosition(x, y, panelWidth, panelHeight);
}

const RealtimeChartValuesPopup: React.FC<RealtimeChartValuesPopupProps> = ({
  anchorEl,
  onClose,
  isDark,
  symbol,
  timeInterval,
  candleData,
  maCalculationData,
  allIndicatorData,
  indicatorData,
  maLines,
  indicatorVisibility,
  ichimokuSettings,
  subPanelsOrder,
  realtimePrice,
  realtimeReceiving,
}) => {
  const nodeRef = useRef<HTMLDivElement>(null);
  const [dragPos, setDragPos] = useState({ x: 24, y: 80 });
  const [panelW, setPanelW] = useState(DEFAULT_PANEL_WIDTH);
  const [panelH, setPanelH] = useState(DEFAULT_PANEL_HEIGHT);
  const [isMaximized, setIsMaximized] = useState(false);
  const preMaxRef = useRef<{ x: number; y: number; w: number; h: number } | null>(null);
  const resizeListenersRef = useRef<{ move: (e: MouseEvent) => void; up: () => void } | null>(null);

  useLayoutEffect(() => {
    if (!anchorEl) return;
    setDragPos(positionNearAnchor(anchorEl, panelW, panelH));
    // 열릴 때(앵커만 변경) 위치만 잡음 — 패널 리사이즈로는 위치 고정
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [anchorEl]);

  const onDragPanel = useCallback(
    (_: unknown, data: { x: number; y: number }) => {
      setDragPos(clampPanelPosition(data.x, data.y, panelW, panelH));
    },
    [panelW, panelH],
  );

  const toggleMaximize = useCallback(() => {
    if (typeof window === 'undefined') return;
    if (!isMaximized) {
      preMaxRef.current = { x: dragPos.x, y: dragPos.y, w: panelW, h: panelH };
      setIsMaximized(true);
      return;
    }
    const saved = preMaxRef.current;
    setIsMaximized(false);
    if (saved) {
      setPanelW(saved.w);
      setPanelH(saved.h);
      setDragPos(clampPanelPosition(saved.x, saved.y, saved.w, saved.h));
    }
    preMaxRef.current = null;
  }, [isMaximized, dragPos.x, dragPos.y, panelW, panelH]);

  const onResizeStart = useCallback(
    (e: React.MouseEvent) => {
      if (isMaximized) return;
      e.preventDefault();
      e.stopPropagation();
      const startX = e.clientX;
      const startY = e.clientY;
      const startW = panelW;
      const startH = panelH;

      const onMove = (ev: MouseEvent) => {
        if (typeof window === 'undefined') return;
        const dw = ev.clientX - startX;
        const dh = ev.clientY - startY;
        const maxW = window.innerWidth - MAXIMIZE_INSET * 2;
        const maxH = window.innerHeight - MAXIMIZE_INSET * 2;
        const nextW = Math.min(maxW, Math.max(MIN_PANEL_WIDTH, startW + dw));
        const nextH = Math.min(maxH, Math.max(MIN_PANEL_HEIGHT, startH + dh));
        setPanelW(nextW);
        setPanelH(nextH);
        setDragPos((p) => clampPanelPosition(p.x, p.y, nextW, nextH));
      };
      const onUp = () => {
        window.removeEventListener('mousemove', onMove);
        window.removeEventListener('mouseup', onUp);
        resizeListenersRef.current = null;
      };
      resizeListenersRef.current = { move: onMove, up: onUp };
      window.addEventListener('mousemove', onMove);
      window.addEventListener('mouseup', onUp);
    },
    [isMaximized, panelW, panelH],
  );

  const snapshot = useMemo(() => {
    if (!candleData.length) return null;
    const lastCandle = candleData[candleData.length - 1];
    const row = pickDisplayIndicatorRow(candleData, indicatorData);
    const candleTimesUnix = new Set(
      candleData.map((c) => candleTimeToSec(c.time)).filter((x) => Number.isFinite(x))
    );
    const lastT = candleTimeToSec(lastCandle.time);
    const sorted = buildSortedDataForMA(allIndicatorData, maCalculationData, candleData);

    const maValues: { id: string; period: number; value: string }[] = [];
    if (sorted.length && maLines?.length) {
      maLines.forEach((ma) => {
        if (!ma.enabled) return;
        if (sorted.length < ma.period) {
          maValues.push({ id: ma.id, period: ma.period, value: '데이터 부족' });
          return;
        }
        const series = calculateSMA(sorted, ma.period).filter(
          (d) => d.value != null && candleTimesUnix.has(d.time)
        );
        const hit = [...series].reverse().find((d) => d.time <= lastT) ?? series[series.length - 1];
        maValues.push({
          id: ma.id,
          period: ma.period,
          value: hit?.value != null ? formatNum(hit.value) : '—',
        });
      });
    }

    return { lastCandle, row, maValues };
  }, [candleData, indicatorData, allIndicatorData, maCalculationData, maLines]);

  const last = snapshot?.lastCandle;
  const row = snapshot?.row;

  const flatValues = useMemo(
    () =>
      buildFlatValuesForFlash(
        snapshot,
        realtimePrice,
        indicatorVisibility,
        ichimokuSettings,
        subPanelsOrder,
      ),
    [snapshot, realtimePrice, indicatorVisibility, ichimokuSettings, subPanelsOrder],
  );

  const prevFlatRef = useRef<Record<string, string>>({});
  const [flashKeys, setFlashKeys] = useState<Set<string>>(() => new Set());
  const liveReceive = realtimeReceiving !== false;
  const isFlashing = useCallback((key: string) => liveReceive && flashKeys.has(key), [liveReceive, flashKeys]);

  useEffect(() => {
    if (!anchorEl) {
      prevFlatRef.current = {};
      setFlashKeys(new Set());
      return;
    }
    if (!liveReceive) {
      prevFlatRef.current = { ...flatValues };
      setFlashKeys(new Set());
      return;
    }
    const cur = flatValues;
    const prev = prevFlatRef.current;
    const changed = new Set<string>();
    for (const key of Object.keys(cur)) {
      if (prev[key] !== undefined && prev[key] !== cur[key]) {
        changed.add(key);
      }
    }
    prevFlatRef.current = { ...cur };
    if (changed.size === 0) return;
    setFlashKeys((s) => {
      const n = new Set<string>();
      s.forEach((k) => n.add(k));
      changed.forEach((k) => n.add(k));
      return n;
    });
    const tid = window.setTimeout(() => {
      setFlashKeys((s) => {
        const n = new Set<string>();
        s.forEach((k) => n.add(k));
        changed.forEach((k) => n.delete(k));
        return n;
      });
    }, FLASH_HIGHLIGHT_MS);
    return () => window.clearTimeout(tid);
  }, [anchorEl, flatValues, liveReceive]);

  useEffect(() => {
    if (!anchorEl) {
      const h = resizeListenersRef.current;
      if (h) {
        window.removeEventListener('mousemove', h.move);
        window.removeEventListener('mouseup', h.up);
        resizeListenersRef.current = null;
      }
      setIsMaximized(false);
    }
  }, [anchorEl]);

  if (!anchorEl || typeof document === 'undefined' || !document.body) {
    return null;
  }

  const shellBox = (
    <Box
      ref={nodeRef}
      sx={{
        position: 'fixed',
        left: isMaximized ? MAXIMIZE_INSET : 0,
        top: isMaximized ? MAXIMIZE_INSET : 0,
        zIndex: (theme) => theme.zIndex.modal + 25,
        width: isMaximized ? `calc(100vw - ${MAXIMIZE_INSET * 2}px)` : panelW,
        height: isMaximized ? `calc(100vh - ${MAXIMIZE_INSET * 2}px)` : panelH,
        pointerEvents: 'auto',
        boxSizing: 'border-box',
      }}
    >
      <Paper
        elevation={8}
        sx={{
          width: '100%',
          height: '100%',
          overflow: 'hidden',
          display: 'flex',
          flexDirection: 'column',
          position: 'relative',
          border: isDark ? '1px solid rgba(255,255,255,0.12)' : '1px solid rgba(0,0,0,0.12)',
          bgcolor: 'background.paper',
        }}
      >
        <Box
          sx={{
            display: 'flex',
            alignItems: 'center',
            gap: 0.5,
            px: 1,
            py: 0.75,
            flexShrink: 0,
            borderBottom: 1,
            borderColor: 'divider',
            bgcolor: isDark ? 'rgba(255,255,255,0.06)' : 'rgba(0,0,0,0.04)',
          }}
        >
          <Box
            className="chart-values-drag-handle"
            sx={{
              display: 'flex',
              alignItems: 'center',
              gap: 0.5,
              flex: 1,
              minWidth: 0,
              cursor: isMaximized ? 'default' : 'grab',
              userSelect: 'none',
              '&:active': { cursor: isMaximized ? 'default' : 'grabbing' },
            }}
          >
            <DragIndicatorIcon sx={{ fontSize: 18, color: 'text.secondary', flexShrink: 0, opacity: 0.85 }} />
            <Typography variant="subtitle2" sx={{ fontWeight: 700, overflow: 'hidden', textOverflow: 'ellipsis' }}>
              실시간 차트 값
            </Typography>
          </Box>
          <IconButton
            className="chart-values-no-drag"
            size="small"
            onClick={toggleMaximize}
            aria-label={isMaximized ? '이전 크기로' : '최대화'}
            sx={{ flexShrink: 0 }}
          >
            {isMaximized ? <FullscreenExitIcon fontSize="small" /> : <FullscreenIcon fontSize="small" />}
          </IconButton>
          <IconButton
            className="chart-values-no-drag"
            size="small"
            onClick={onClose}
            aria-label="닫기"
            sx={{ flexShrink: 0 }}
          >
            <CloseIcon fontSize="small" />
          </IconButton>
        </Box>

        <Box sx={{ overflow: 'auto', px: 1.25, pb: 1.5, flex: 1, minHeight: 0 }}>
          <Typography variant="caption" color="text.secondary" sx={{ display: 'block', mt: 1 }}>
            {symbol} · {timeInterval}
          </Typography>

          {!last ? (
            <Typography variant="body2" color="text.secondary" sx={{ mt: 2 }}>
              표시할 캔들 데이터가 없습니다.
            </Typography>
          ) : (
            <>
              {sectionTitle('캔들 (마지막 봉)')}
              <Row label="시간" value={String(last.time)} flash={isFlashing('candle:time')} />
              <Row label="시가(O)" value={formatNum(last.open)} flash={isFlashing('candle:open')} />
              <Row label="고가(H)" value={formatNum(last.high)} flash={isFlashing('candle:high')} />
              <Row label="저가(L)" value={formatNum(last.low)} flash={isFlashing('candle:low')} />
              <Row label="종가(C)" value={formatNum(last.close)} flash={isFlashing('candle:close')} />
              <Row label="거래량(V)" value={formatNum(last.volume)} flash={isFlashing('candle:volume')} />
              {realtimePrice != null && Number.isFinite(realtimePrice) && (
                <Row label="실시간 체결" value={formatNum(realtimePrice)} flash={isFlashing('candle:rt')} />
              )}

              {snapshot?.maValues && snapshot.maValues.length > 0 && (
                <>
                  {sectionTitle('이동평균선 (SMA)')}
                  {snapshot.maValues.map((m) => (
                    <Row
                      key={m.id}
                      label={`${m.id} (${m.period})`}
                      value={m.value}
                      flash={isFlashing(`ma:${m.id}`)}
                    />
                  ))}
                </>
              )}

              {row &&
                ichimokuSettings &&
                (ichimokuSettings.tenkan ||
                  ichimokuSettings.kijun ||
                  ichimokuSettings.chikou ||
                  ichimokuSettings.senkouA ||
                  ichimokuSettings.senkouB) && (
                  <>
                    {sectionTitle('일목균형표')}
                    {ichimokuSettings.tenkan && (
                      <Row label="전환선" value={formatNum(row.ichimokuTenkan)} flash={isFlashing('ichimoku:tenkan')} />
                    )}
                    {ichimokuSettings.kijun && (
                      <Row label="기준선" value={formatNum(row.ichimokuKijun)} flash={isFlashing('ichimoku:kijun')} />
                    )}
                    {ichimokuSettings.chikou && (
                      <Row label="후행스팬" value={formatNum(row.ichimokuChikou)} flash={isFlashing('ichimoku:chikou')} />
                    )}
                    {ichimokuSettings.senkouA && (
                      <Row label="선행스팬 A" value={formatNum(row.ichimokuSenkouA)} flash={isFlashing('ichimoku:senkouA')} />
                    )}
                    {ichimokuSettings.senkouB && (
                      <Row label="선행스팬 B" value={formatNum(row.ichimokuSenkouB)} flash={isFlashing('ichimoku:senkouB')} />
                    )}
                  </>
                )}

              {row &&
                (indicatorVisibility.bollingerUpper ||
                  indicatorVisibility.bollingerMiddle ||
                  indicatorVisibility.bollingerLower) && (
                  <>
                    {sectionTitle(
                      `볼린저 밴드 (${indicatorVisibility.bollingerPeriod}/${indicatorVisibility.bollingerStdDev})`
                    )}
                    {indicatorVisibility.bollingerUpper && (
                      <Row label="상한" value={formatNum(row.bollingerUpper)} flash={isFlashing('bb:upper')} />
                    )}
                    {indicatorVisibility.bollingerMiddle && (
                      <Row label="중심" value={formatNum(row.bollingerMiddle)} flash={isFlashing('bb:middle')} />
                    )}
                    {indicatorVisibility.bollingerLower && (
                      <Row label="하한" value={formatNum(row.bollingerLower)} flash={isFlashing('bb:lower')} />
                    )}
                  </>
                )}

              {row && subPanelsOrder.some((t) => panelEnabled(t, indicatorVisibility)) && (
                <>
                  {sectionTitle('보조지표 패널 (마지막 봉)')}
                  {subPanelsOrder.map((type) => {
                    if (!panelEnabled(type, indicatorVisibility)) return null;
                    const rows = subPanelRows(type, row);
                    if (!rows.length) return null;
                    return (
                      <Box key={type} sx={{ mb: 0.75 }}>
                        <Typography variant="caption" sx={{ fontWeight: 600, color: 'primary.main' }}>
                          {type.toUpperCase()}
                        </Typography>
                        {rows.map((x) => (
                          <Row
                            key={`${type}-${x.label}`}
                            label={x.label}
                            value={x.value}
                            flash={isFlashing(`sub:${type}:${x.label}`)}
                          />
                        ))}
                      </Box>
                    );
                  })}
                </>
              )}
            </>
          )}
        </Box>

        {!isMaximized && (
          <Box
            className="chart-values-no-drag"
            role="presentation"
            aria-hidden
            onMouseDown={onResizeStart}
            sx={{
              position: 'absolute',
              right: 0,
              bottom: 0,
              width: 22,
              height: 22,
              cursor: 'nwse-resize',
              zIndex: 2,
              background: isDark
                ? 'linear-gradient(135deg, transparent 50%, rgba(255,255,255,0.12) 50%)'
                : 'linear-gradient(135deg, transparent 50%, rgba(0,0,0,0.08) 50%)',
            }}
          />
        )}
      </Paper>
    </Box>
  );

  const panel = isMaximized ? (
    shellBox
  ) : (
    <Draggable
      nodeRef={nodeRef}
      handle=".chart-values-drag-handle"
      position={dragPos}
      onDrag={onDragPanel}
      bounds="body"
      cancel=".chart-values-no-drag"
    >
      {shellBox}
    </Draggable>
  );

  return createPortal(panel, document.body);
};

export default RealtimeChartValuesPopup;
