/**
 * PaneLegend
 * 각 서브 pane 좌측 상단에 인디케이터 명칭 + 현재 크로스헤어 값 표시.
 * 드래그 핸들(⠿) / 전체화면 / 닫기 버튼 포함.
 *
 * ─── 패인 인덱스 구조 ───────────────────────────────────────────────────────
 *  Pane 0  : 메인 캔들차트
 *  Pane 1  : 볼륨 (항상 고정, ChartManager._getNextSubPane 이 2부터 시작하는 이유)
 *  Pane 2+ : 보조지표 (RSI=2, MACD=3, ...)
 * ────────────────────────────────────────────────────────────────────────────
 */
import React, { useState, useEffect, useCallback, useRef } from 'react';
import type { MouseEventParams, Time } from 'lightweight-charts';
import type { IndicatorConfig } from '../types';
import { ChartManager } from '../utils/ChartManager';
import { getIndicatorDef, getIndicatorChartTitle } from '../utils/indicatorRegistry';
import { getGlobalParamKeys, getPlotParamKeys } from '../utils/indicatorSettingsLayout';
import { formatBbLegendLabel } from '../utils/bollingerConfig';
import { bindWindowDrag, clientXYFromReact } from '../utils/pointerDrag';
import { getPaneHostId } from '../utils/indicatorPaneMerge';

// ── 타입 ─────────────────────────────────────────────────────────────────────
interface PaneItem {
  id:         string;
  paneIndex:  number;
  label:      string;
  plotColors: string[];
  hidden?:    boolean;
  /** 화면에 보이는 sub-pane 위치 (orphan pane 인덱스와 분리) */
  layoutTopY?:    number;
  layoutHeight?:  number;
}

const MIN_SUB_PANE_HEIGHT = 12;

interface DomPaneLayout {
  topY:    number;
  height:  number;
}

/**
 * LWC 테이블 DOM 에서 보조지표 pane 행만 읽음 (pane index 무관).
 * orphan 빈 pane 행이 있으면 제외하고 하단 expectCount 개만 사용.
 */
function getDomSubIndicatorLayouts(
  containerEl: HTMLElement,
  expectCount: number,
): DomPaneLayout[] {
  const containerRect = containerEl.getBoundingClientRect();
  const rows = Array.from(containerEl.querySelectorAll('tr'))
    .map(tr => tr.getBoundingClientRect())
    .filter(rect => rect.height > MIN_SUB_PANE_HEIGHT)
    .sort((a, b) => a.top - b.top);

  // pane 0=캔들(첫 행). 둘째 행이 낮으면 거래량 pane → 보조지표는 그 다음부터
  let startIdx = 1;
  if (rows.length > 1 && rows[1].height < Math.min(100, rows[0].height * 0.35)) {
    startIdx = 2;
  }

  let subs = rows.slice(startIdx).map(rect => ({
    topY:   Math.round(rect.top - containerRect.top),
    height: Math.round(rect.height),
  }));

  if (expectCount > 0 && subs.length > expectCount) {
    subs = subs.slice(subs.length - expectCount);
  }
  return subs;
}

/** 보조지표 1개 = 1 화면 슬롯 (병합 시 여러 item 이 한 슬롯) */
interface VisualSlot {
  items: PaneItem[];
}

interface PaneCapture {
  dataUrl:   string;
  cssWidth:  number;
  cssHeight: number;
}

type DropMode = 'reorder' | 'merge';

interface DragState {
  draggingId:      string;
  ghostLabel:      string;
  ghostX:          number;
  ghostCursorY:    number;
  dropBeforeIndex: number;
  dropMode:        DropMode;
  mergeTargetId:   string | null;
  paneCapture:     PaneCapture | null;
}

export interface PaneLegendProps {
  manager:        ChartManager;
  indicators:     IndicatorConfig[];
  containerEl:    HTMLElement;
  onHoverName:    (id: string, sx: number, sy: number) => void;
  onLeaveName:    () => void;
  onDoubleClick?: (id: string) => void;
  onReorder?:     (fromId: string, insertBeforeId: string | null) => void;
  /** 보조지표 pane 병합 */
  onMerge?:       (fromId: string, intoId: string) => void;
  /** 병합 pane 분리 */
  onSplit?:       (hostId: string) => void;
  /** 지표 단독 전체화면 확장 */
  onExpand?:      (id: string) => void;
  /** 지표 제거 (X 버튼) */
  onRemove?:      (id: string) => void;
  /** 보조지표 pane 복사 */
  onDuplicate?:   (id: string) => void;
  /** 현재 단독 전체화면 중인 지표 id */
  focusedId?:     string | null;
  /** 전체화면 → 전체 보기 복원 */
  onRestore?:     () => void;
}

// ── 지그재그 배경색 — 홀짝 2단계로만 번갈아 적용 ───────────────────────────
const PANE_BG_COLORS = [
  'rgba(255, 255, 255, 0.030)',  // 짝수(0,2,4…): 아주 살짝 밝음
  'rgba(255, 255, 255, 0.000)',  // 홀수(1,3,5…): 기본 배경(투명)
];

// ── 캔버스 캡처 ───────────────────────────────────────────────────────────────
function capturePaneRegion(
  containerEl: HTMLElement,
  paneTopY: number,
  paneHeight: number,
): PaneCapture | null {
  const rows = Array.from(containerEl.querySelectorAll<HTMLTableRowElement>('tr'));
  let targetRow: HTMLTableRowElement | null = null;
  for (const row of rows) {
    if (Math.abs(row.offsetTop - paneTopY) < 8) { targetRow = row; break; }
  }

  const canvases = Array.from(
    (targetRow ?? containerEl).querySelectorAll<HTMLCanvasElement>('canvas'),
  ).filter(c => c.width > 0 && c.height > 0);

  if (canvases.length === 0) return null;
  const cssWidth = containerEl.clientWidth;

  if (targetRow) {
    const ref = canvases[0];
    const off = document.createElement('canvas');
    off.width = ref.width; off.height = ref.height;
    const ctx = off.getContext('2d');
    if (!ctx) return null;
    for (const c of canvases) { try { ctx.drawImage(c, 0, 0); } catch { /* skip */ } }
    try { return { dataUrl: off.toDataURL('image/webp', 0.92), cssWidth, cssHeight: paneHeight }; }
    catch { return null; }
  }

  const ref    = canvases[0];
  const scaleY = ref.height / (containerEl.clientHeight || 1);
  const srcY   = Math.round(paneTopY * scaleY);
  const srcH   = Math.round(paneHeight * scaleY);
  if (srcH <= 0) return null;
  const off = document.createElement('canvas');
  off.width = ref.width; off.height = srcH;
  const ctx = off.getContext('2d');
  if (!ctx) return null;
  for (const c of canvases) {
    const cs = c.height / (containerEl.clientHeight || 1);
    try { ctx.drawImage(c, 0, Math.round(paneTopY * cs), c.width, Math.round(paneHeight * cs), 0, 0, ref.width, srcH); }
    catch { /* skip */ }
  }
  try { return { dataUrl: off.toDataURL('image/webp', 0.92), cssWidth, cssHeight: paneHeight }; }
  catch { return null; }
}

// ── 헬퍼 ─────────────────────────────────────────────────────────────────────

/**
 * 드래그 중 각 pane의 translateY 오프셋 계산.
 * draggingIdx → dropBeforeIdx 방향으로 이동 시 중간 pane들을 반대 방향으로 밀어낸다.
 */
function computeShiftY(
  itemIdx:      number,   // 이 pane의 현재 배열 인덱스
  draggingIdx:  number,   // 드래그 중인 pane의 인덱스
  dropBefore:   number,   // 드롭 목표: 이 인덱스 앞에 삽입
  dragHeight:   number,   // 드래그 중인 pane의 높이(px)
): number {
  if (itemIdx === draggingIdx) return 0;        // 드래그 중인 항목: 고스트로 대체
  if (draggingIdx < dropBefore) {
    // 아래로 이동 → 사이 pane 들을 위로
    if (itemIdx > draggingIdx && itemIdx < dropBefore) return -dragHeight;
  } else if (draggingIdx > dropBefore) {
    // 위로 이동 → 사이 pane 들을 아래로
    if (itemIdx >= dropBefore && itemIdx < draggingIdx) return dragHeight;
  }
  return 0;
}

function makeLabel(config: IndicatorConfig): string {
  if (config.type === 'BollingerBands') {
    return formatBbLegendLabel(config.params ?? {});
  }
  const name = getIndicatorChartTitle(config.type);
  const def = getIndicatorDef(config.type);
  const params = (config.params ?? def?.defaultParams ?? {}) as Record<string, number | string | boolean>;
  const plots = config.plots ?? def?.plots ?? [];

  const orderedKeys: string[] = [];
  for (let i = 0; i < plots.length; i++) {
    for (const k of getPlotParamKeys(config.type, plots[i].id, i, plots, params)) {
      if (!orderedKeys.includes(k)) orderedKeys.push(k);
    }
  }
  for (const k of getGlobalParamKeys(config.type, params, plots)) {
    if (!orderedKeys.includes(k) && typeof params[k] === 'number') orderedKeys.push(k);
  }

  const nums = orderedKeys
    .map(k => params[k])
    .filter((v): v is number => typeof v === 'number')
    .slice(0, 3);
  return nums.length ? `${name} ${nums.join(', ')}` : name;
}

function buildVisualSlots(items: PaneItem[], indicators: IndicatorConfig[]): VisualSlot[] {
  const slots: VisualSlot[] = [];
  const hostSlotIdx = new Map<string, number>();

  for (const item of items) {
    const ind = indicators.find(i => i.id === item.id);
    if (ind?.mergedWith) {
      const hostId = getPaneHostId(item.id, indicators);
      const idx = hostSlotIdx.get(hostId);
      if (idx !== undefined) slots[idx].items.push(item);
      continue;
    }
    const idx = slots.length;
    slots.push({ items: [item] });
    hostSlotIdx.set(item.id, idx);
    hostSlotIdx.set(getPaneHostId(item.id, indicators), idx);
  }
  return slots;
}

/** 각 슬롯(호스트 paneIndex) ↔ LWC pane 레이아웃 직접 매칭 — 배열 순서·orphan pane 무관 */
function assignLayoutsByPaneIndex(
  slots: VisualSlot[],
  layouts: Array<{ paneIndex: number; topY: number; height: number }>,
): void {
  const byPane = new Map(layouts.map(l => [l.paneIndex, l]));
  for (const slot of slots) {
    const host = slot.items[0];
    if (!host || host.paneIndex < 2) continue;
    const lay = byPane.get(host.paneIndex);
    if (!lay || lay.height < MIN_SUB_PANE_HEIGHT) continue;
    for (const item of slot.items) {
      item.layoutTopY = lay.topY;
      item.layoutHeight = lay.height;
    }
  }
}

function buildPaneItems(
  manager: ChartManager,
  containerEl: HTMLElement | null,
  indicators: IndicatorConfig[],
  optPaneMap?: Map<string, number>,
): PaneItem[] {
  const paneMap = new Map(optPaneMap ?? []);
  for (const info of manager.getIndicatorPaneInfo()) {
    paneMap.set(info.id, info.paneIndex);
  }

  const items: PaneItem[] = [];
  for (const ind of indicators) {
    const def = getIndicatorDef(ind.type);
    if (!def || def.overlay || def.returnsMarkers) continue;
    const plotColors = (ind.plots ?? def.plots ?? []).map(p => (p.color ?? '#aaa').slice(0, 7));

    let chartPane = paneMap.get(ind.id) ?? -1;
    if (ind.mergedWith) {
      const hostPane = paneMap.get(ind.mergedWith);
      if (hostPane != null && hostPane >= 2) chartPane = hostPane;
    }

    items.push({
      id: ind.id,
      paneIndex: chartPane,
      label: makeLabel(ind),
      plotColors,
      hidden: ind.hidden === true,
    });
  }

  if (items.length === 0) return [];

  for (const item of items) {
    const lay = manager.getIndicatorPaneScreenLayout(item.id);
    if (lay) {
      item.layoutTopY = lay.topY;
      item.layoutHeight = lay.height;
    }
  }

  const slots = buildVisualSlots(items, indicators);
  assignLayoutsByPaneIndex(slots, manager.getPaneLayouts());

  if (containerEl?.isConnected) {
    const missing = items.filter(i => i.layoutTopY == null);
    if (missing.length > 0) {
      const domSubs = getDomSubIndicatorLayouts(containerEl, slots.length);
      for (let i = 0; i < slots.length && i < domSubs.length; i++) {
        if (slots[i].items.every(it => it.layoutTopY != null)) continue;
        const dom = domSubs[i];
        for (const item of slots[i].items) {
          if (item.layoutTopY != null) continue;
          item.layoutTopY = dom.topY;
          item.layoutHeight = dom.height;
        }
      }
    }
  }

  return items.filter(i => i.layoutTopY != null && i.layoutHeight != null);
}

function paneItemLayout(
  item: PaneItem,
  layouts: Array<{ paneIndex: number; topY: number; height: number }>,
): { topY: number; height: number } | null {
  if (item.layoutTopY != null && item.layoutHeight != null) {
    return { topY: item.layoutTopY, height: item.layoutHeight };
  }
  const l = layouts.find(
    x => x.paneIndex === item.paneIndex && x.height > MIN_SUB_PANE_HEIGHT,
  );
  return l ? { topY: l.topY, height: l.height } : null;
}

// ── 아이콘 SVG ────────────────────────────────────────────────────────────────
/** ⤡ 전체화면 확장 — 네 꼭지점 바깥 방향 화살표 */
const IconExpand = () => (
  <svg width="13" height="13" viewBox="0 0 14 14" fill="none"
    stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
    {/* 좌하단 → 좌하 */}
    <polyline points="1,9 1,13 5,13"/>
    <line x1="1" y1="13" x2="5.5" y2="8.5"/>
    {/* 우상단 → 우상 */}
    <polyline points="13,5 13,1 9,1"/>
    <line x1="13" y1="1" x2="8.5" y2="5.5"/>
  </svg>
);

/** ⤢ 전체화면 복원 — 네 꼭지점 안쪽 방향 화살표 */
const IconRestore = () => (
  <svg width="13" height="13" viewBox="0 0 14 14" fill="none"
    stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
    {/* 좌하단 → 중앙 */}
    <polyline points="5,13 1,13 1,9"/>
    <line x1="1" y1="13" x2="5.5" y2="8.5"/>
    {/* 우상단 → 중앙 */}
    <polyline points="9,1 13,1 13,5"/>
    <line x1="13" y1="1" x2="8.5" y2="5.5"/>
  </svg>
);

const IconMerge = () => (
  <svg width="28" height="28" viewBox="0 0 28 28" fill="none"
    stroke="currentColor" strokeWidth="2.2" strokeLinecap="round">
    <line x1="14" y1="6" x2="14" y2="22"/>
    <line x1="6" y1="14" x2="22" y2="14"/>
  </svg>
);

/** 그래프 복사 — 겹친 사각형 */
const IconCopy = () => (
  <svg width="13" height="13" viewBox="0 0 14 14" fill="none"
    stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round">
    <rect x="4.5" y="1.5" width="8" height="8" rx="1"/>
    <path d="M1.5 4.5v8h8"/>
  </svg>
);

const IconSplit = () => (
  <svg width="13" height="13" viewBox="0 0 14 14" fill="none"
    stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
    <polyline points="1,7 5,3 5,11"/>
    <polyline points="13,7 9,3 9,11"/>
    <line x1="5" y1="7" x2="9" y2="7"/>
  </svg>
);

const IconClose = () => (
  <svg width="11" height="11" viewBox="0 0 11 11" fill="none"
    stroke="currentColor" strokeWidth="2" strokeLinecap="round">
    <line x1="1.5" y1="1.5" x2="9.5" y2="9.5"/>
    <line x1="9.5" y1="1.5" x2="1.5" y2="9.5"/>
  </svg>
);

// ── 컴포넌트 ──────────────────────────────────────────────────────────────────
const PaneLegend: React.FC<PaneLegendProps> = ({
  manager, indicators, containerEl,
  onHoverName, onLeaveName, onDoubleClick,
  onReorder, onMerge, onSplit, onExpand, onRemove, onDuplicate, focusedId, onRestore,
}) => {
  const [paneItems,   setPaneItems]   = useState<PaneItem[]>([]);
  const [paneLayouts, setPaneLayouts] = useState<Array<{ paneIndex: number; topY: number; height: number }>>([]);
  const [values,      setValues]      = useState<Record<string, number[]>>({});
  const [dragState,   setDragState]   = useState<DragState | null>(null);
  const retryRef = useRef<ReturnType<typeof setTimeout>[]>([]);
  const dragRef  = useRef<{
    id: string;
    dropBeforeIndex: number;
    dropMode: DropMode;
    mergeTargetId: string | null;
  } | null>(null);

  const paneItemsRef   = useRef(paneItems);
  const paneLayoutsRef = useRef(paneLayouts);
  const containerElRef = useRef(containerEl);
  const onReorderRef   = useRef(onReorder);
  const onMergeRef     = useRef(onMerge);
  const onSplitRef     = useRef(onSplit);
  const onDuplicateRef = useRef(onDuplicate);
  const indicatorsRef  = useRef(indicators);

  useEffect(() => { paneItemsRef.current   = paneItems;   }, [paneItems]);
  useEffect(() => { paneLayoutsRef.current = paneLayouts; }, [paneLayouts]);
  useEffect(() => { containerElRef.current = containerEl; }, [containerEl]);
  useEffect(() => { onReorderRef.current   = onReorder;   }, [onReorder]);
  useEffect(() => { onMergeRef.current     = onMerge;     }, [onMerge]);
  useEffect(() => { onSplitRef.current     = onSplit;     }, [onSplit]);
  useEffect(() => { onDuplicateRef.current = onDuplicate; }, [onDuplicate]);
  useEffect(() => { indicatorsRef.current  = indicators;  }, [indicators]);

  const syncPaneItems = useCallback(() => {
    const el = containerElRef.current;
    if (!el?.isConnected) return;
    const map = manager.getIndicatorPaneMapping();
    setPaneLayouts(manager.getPaneLayouts());
    setPaneItems(buildPaneItems(manager, el, indicators, map));
  }, [indicators, manager]);

  const refreshLayouts = useCallback(() => {
    setPaneLayouts(manager.getPaneLayouts());
  }, [manager]);

  const scheduleRetry = useCallback(() => {
    syncPaneItems();
    retryRef.current.forEach(clearTimeout);
    retryRef.current = [
      setTimeout(syncPaneItems, 0),
      setTimeout(syncPaneItems, 16),
      setTimeout(syncPaneItems, 50),
      setTimeout(syncPaneItems, 120),
      setTimeout(syncPaneItems, 250),
      setTimeout(syncPaneItems, 500),
      setTimeout(syncPaneItems, 900),
      setTimeout(syncPaneItems, 1200),
      setTimeout(syncPaneItems, 1800),
    ];
  }, [syncPaneItems]);

  useEffect(() => { scheduleRetry(); return () => retryRef.current.forEach(clearTimeout); }, [scheduleRetry]);

  useEffect(() => {
    const unsub = manager.subscribePaneLayout(() => scheduleRetry());
    return unsub;
  }, [manager, scheduleRetry]);

  useEffect(() => {
    const onResize = () => {
      refreshLayouts();
      syncPaneItems();
    };
    const ro = new ResizeObserver(onResize);
    ro.observe(containerEl);
    return () => ro.disconnect();
  }, [containerEl, refreshLayouts, syncPaneItems]);

  useEffect(() => {
    const unsub = manager.subscribeCrosshair((p: MouseEventParams<Time>) => {
      const map = manager.getIndicatorPaneMapping();
      const layouts = manager.getPaneLayouts();
      setPaneLayouts(layouts);
      const el = containerElRef.current;
      if (el?.isConnected) {
        setPaneItems(buildPaneItems(manager, el, indicatorsRef.current, map));
      }
      if (!p.time) { setValues({}); return; }
      setValues(manager.getIndicatorValuesByIdFromParams(p));
    });
    return unsub;
  }, [manager]);

  // ── 드롭 라인 Y ────────────────────────────────────────────────────────────
  const getDropLineY = useCallback((idx: number): number | null => {
    const items   = paneItemsRef.current;
    const layouts = paneLayoutsRef.current;
    if (!items.length) return null;
    const rect = containerElRef.current.getBoundingClientRect();
    if (idx <= 0) {
      const pl = paneItemLayout(items[0], layouts);
      return pl ? rect.top + pl.topY : null;
    }
    if (idx >= items.length) {
      const pl = paneItemLayout(items[items.length - 1], layouts);
      return pl ? rect.top + pl.topY + pl.height : null;
    }
    const pl = paneItemLayout(items[idx - 1], layouts);
    return pl ? rect.top + pl.topY + pl.height : null;
  }, []);

  const unbindPaneDragRef = useRef<(() => void) | null>(null);

  // ── 드래그 핸들 (마우스·터치) ───────────────────────────────────────────
  const handleDragHandlePointerDown = useCallback((e: React.PointerEvent, id: string) => {
    if (!onReorderRef.current) return;
    if (e.button !== 0) return;
    e.preventDefault();
    e.stopPropagation();
    try {
      (e.currentTarget as HTMLElement).setPointerCapture(e.pointerId);
    } catch {
      /* ignore */
    }
    const item      = paneItemsRef.current.find(i => i.id === id);
    const fromIndex = paneItemsRef.current.findIndex(i => i.id === id);
    if (!item || fromIndex === -1) return;

    const layout     = paneItemLayout(item, paneLayoutsRef.current);
    const paneCapture = layout
      ? capturePaneRegion(containerElRef.current, layout.topY, layout.height)
      : null;
    const { x, y } = clientXYFromReact(e);

    dragRef.current = {
      id,
      dropBeforeIndex: fromIndex,
      dropMode: 'reorder',
      mergeTargetId: null,
    };
    setDragState({
      draggingId: id,
      ghostLabel: item.label,
      ghostX: x + 14,
      ghostCursorY: y,
      dropBeforeIndex: fromIndex,
      dropMode: 'reorder',
      mergeTargetId: null,
      paneCapture,
    });

    document.body.style.cursor = 'grabbing';
    document.body.style.userSelect = 'none';

    const handleKeyDown = (ev: KeyboardEvent) => { if (ev.key === 'Escape') cleanup(); };

    const cleanup = () => {
      dragRef.current = null;
      setDragState(null);
      document.body.style.cursor = '';
      document.body.style.userSelect = '';
      unbindPaneDragRef.current?.();
      unbindPaneDragRef.current = null;
      document.removeEventListener('keydown', handleKeyDown);
    };

    const finishDrag = () => {
      const dr = dragRef.current;
      if (dr) {
        if (dr.dropMode === 'merge' && dr.mergeTargetId) {
          onMergeRef.current?.(dr.id, dr.mergeTargetId);
        } else {
          const items = paneItemsRef.current;
          const fi = items.findIndex(i => i.id === dr.id);
          const noOp = dr.dropBeforeIndex === fi || dr.dropBeforeIndex === fi + 1;
          if (!noOp) {
            const before = dr.dropBeforeIndex >= items.length
              ? null
              : items[dr.dropBeforeIndex].id;
            onReorderRef.current?.(dr.id, before);
          }
        }
      }
      cleanup();
    };

    unbindPaneDragRef.current?.();
    unbindPaneDragRef.current = bindWindowDrag(
      ({ x: cx, y: cy }) => {
        if (!dragRef.current) return;
        const items = paneItemsRef.current;
        const layouts = paneLayoutsRef.current;
        const inds = indicatorsRef.current;
        const rect = containerElRef.current.getBoundingClientRect();
        const mouseY = cy - rect.top;
        const dragHost = getPaneHostId(dragRef.current.id, inds);

        let dropMode: DropMode = 'reorder';
        let mergeTargetId: string | null = null;
        let drop = items.length;

        for (const item of items) {
          if (getPaneHostId(item.id, inds) === dragHost) continue;
          const l = paneItemLayout(item, layouts);
          if (!l || mouseY < l.topY || mouseY >= l.topY + l.height) continue;
          const rel = (mouseY - l.topY) / Math.max(l.height, 1);
          if (rel > 0.28 && rel < 0.72) {
            dropMode = 'merge';
            mergeTargetId = item.id;
            break;
          }
        }

        if (dropMode === 'reorder') {
          for (let i = 0; i < items.length; i++) {
            const l = paneItemLayout(items[i], layouts);
            if (l && mouseY < l.topY + l.height / 2) {
              drop = i;
              break;
            }
          }
        }

        dragRef.current.dropBeforeIndex = drop;
        dragRef.current.dropMode = dropMode;
        dragRef.current.mergeTargetId = mergeTargetId;
        document.body.style.cursor = dropMode === 'merge' ? 'copy' : 'grabbing';

        setDragState(prev => prev ? {
          ...prev,
          dropBeforeIndex: drop,
          dropMode,
          mergeTargetId,
          ghostX: cx + 14,
          ghostCursorY: cy,
        } : null);
      },
      finishDrag,
    );
    document.addEventListener('keydown', handleKeyDown);
  }, []);

  // ── 렌더 ─────────────────────────────────────────────────────────────────
  const canReorder       = !!onReorder && paneItems.length > 1;
  const isDragging       = dragState !== null;
  const dropLineY        = isDragging && dragState!.dropMode === 'reorder'
    ? getDropLineY(dragState!.dropBeforeIndex)
    : null;
  const containerRect    = containerEl.getBoundingClientRect();

  const draggingHostId = isDragging
    ? getPaneHostId(dragState!.draggingId, indicators)
    : '';

  const draggingItemIdx = isDragging
    ? paneItems.findIndex(i => i.id === draggingHostId)
    : -1;

  const draggingLayout = draggingItemIdx >= 0
    ? paneItemLayout(paneItems[draggingItemIdx], paneLayouts)
    : null;

  const mergeTargetLayout = isDragging && dragState?.mergeTargetId
    ? (() => {
        const t = paneItems.find(i => i.id === dragState.mergeTargetId);
        return t ? paneItemLayout(t, paneLayouts) : null;
      })()
    : null;

  return (
    <>
      {paneItems.map((item, itemIdx) => {
        const pl = paneItemLayout(item, paneLayouts);
        if (!pl) return null;

        const screenX        = containerRect.left;
        const screenY        = containerRect.top + pl.topY;
        const paneBottomY    = containerRect.top + pl.topY + pl.height;
        const itemVals       = item.hidden ? [] : (values[item.id] ?? []);
        const itemHostId     = getPaneHostId(item.id, indicators);
        const samePaneItems  = paneItems.filter(p => p.paneIndex === item.paneIndex);
        const stackIdx       = samePaneItems.findIndex(p => p.id === item.id);
        const isBeingDragged = isDragging && itemHostId === draggingHostId;
        const isFocused      = focusedId === item.id;
        const isMergedPane   = samePaneItems.length > 1;
        const showSplitBtn   = isMergedPane && item.id === itemHostId && !!onSplit;
        const PANE_BTN_STEP  = 24;
        const paneBtnRight   = (slot: number) => containerRect.right - 28 - slot * PANE_BTN_STEP;
        let btnSlot = 0;
        const closeSlot = btnSlot++;
        const copySlot  = onDuplicate ? btnSlot++ : -1;
        const splitSlot = showSplitBtn ? btnSlot++ : -1;
        const isPaneHidden   = item.hidden === true;
        const bgColor        = PANE_BG_COLORS[itemIdx % PANE_BG_COLORS.length];

        // ── 드래그 중: 이 pane의 translateY 오프셋 ──────────────────────────
        const shiftY = (isDragging && draggingItemIdx >= 0 && draggingLayout)
          ? computeShiftY(
              itemIdx,
              draggingItemIdx,
              dragState!.dropMode === 'merge' ? draggingItemIdx : dragState!.dropBeforeIndex,
              draggingLayout.height,
            )
          : 0;
        const legendTop = screenY + 5 + stackIdx * 22;
        // 드래그 중 shift 전환은 transform, 드래그 후 위치 재정렬은 top
        const shiftTrans = isDragging
          ? 'transform 0.18s ease'
          : 'top 0.22s ease, height 0.22s ease, transform 0.18s ease';

        return (
          <React.Fragment key={item.id}>
            {/* ── 배경색 오버레이 ─────────────────────────────────────── */}
            <div
              className="pane-bg-overlay"
              style={{
                position:   'fixed',
                left:        screenX,
                top:         screenY,
                width:       containerRect.width,
                height:      pl.height,
                background:  isBeingDragged
                  ? 'transparent'
                  : isDragging
                    ? bgColor.replace(/[\d.]+\)$/, '0.12)')  // 드래그 중 살짝 강조
                    : bgColor,
                zIndex:      480,
                pointerEvents: 'none',
                transform:   `translateY(${shiftY}px)`,
                transition:  shiftTrans + ', background 0.12s ease',
              }}
            />

            {/* ── 레전드 레이블 (pane 좌측 상단) ─────────────────────── */}
            <div
              className="pane-legend-item"
              style={{
                position:     'fixed',
                left:          screenX + 6,
                top:           legendTop,
                zIndex:        502,
                pointerEvents: isDragging ? 'none' : 'auto',
                display:       'flex',
                alignItems:    'center',
                gap:           '4px',
                opacity:       isBeingDragged ? 0 : 1,
                transform:     `translateY(${shiftY}px)`,
                transition:    shiftTrans + ', opacity 0.12s ease',
              }}
            >
              <span
                className={`pane-legend-name${isPaneHidden ? ' pane-legend-name--hidden' : ''}`}
                onMouseEnter={() => !isDragging && onHoverName(item.id, screenX, screenY)}
                onMouseLeave={() => !isDragging && onLeaveName()}
                onDoubleClick={() => !isDragging && onDoubleClick?.(item.id)}
              >
                {item.label}
              </span>
              {itemVals.map((v, i) => isNaN(v) ? null : (
                <span key={i} className="pane-legend-val"
                  style={{ color: item.plotColors[i] ?? 'var(--text)' }}>
                  {formatVal(v)}
                </span>
              ))}
            </div>

            {/* ── 우측 상단: 분리 / 복사 / 닫기 ─────────────────────── */}
            {showSplitBtn && splitSlot >= 0 && (
              <button
                className="pane-btn pane-btn-split"
                style={{
                  position:   'fixed',
                  left:        paneBtnRight(splitSlot),
                  top:         screenY + 5,
                  zIndex:      505,
                  opacity:     isBeingDragged ? 0 : undefined,
                  transform:   `translateY(${shiftY}px)`,
                  transition:  shiftTrans,
                }}
                onClick={e => {
                  e.stopPropagation();
                  onSplitRef.current?.(itemHostId);
                }}
                title="보조지표 분리"
              >
                <IconSplit />
              </button>
            )}
            {copySlot >= 0 && (
              <button
                className="pane-btn pane-btn-copy"
                style={{
                  position:   'fixed',
                  left:        paneBtnRight(copySlot),
                  top:         screenY + 5,
                  zIndex:      505,
                  opacity:     isBeingDragged ? 0 : undefined,
                  transform:   `translateY(${shiftY}px)`,
                  transition:  shiftTrans,
                }}
                onClick={e => {
                  e.stopPropagation();
                  onDuplicateRef.current?.(item.id);
                }}
                title="보조지표 복사"
              >
                <IconCopy />
              </button>
            )}
            {onRemove && (
              <button
                className="pane-btn pane-btn-close"
                style={{
                  position:   'fixed',
                  left:        paneBtnRight(closeSlot),
                  top:         screenY + 5,
                  zIndex:      504,
                  opacity:     isBeingDragged ? 0 : undefined,
                  transform:   `translateY(${shiftY}px)`,
                  transition:  shiftTrans,
                }}
                onClick={e => { e.stopPropagation(); onRemove(item.id); }}
                title="지표 제거"
              >
                <IconClose />
              </button>
            )}

            {/* ── 좌측 하단: 전체화면 버튼 (위) + 드래그 핸들 (아래) ── */}
            {/* 드래그 핸들: pane 하단에서 30px 위 */}
            {canReorder && (
              <div
                className="pane-drag-handle"
                style={{
                  position:   'fixed',
                  left:        screenX + 4,
                  top:         paneBottomY - 30,
                  zIndex:      510,
                  opacity:     isBeingDragged ? 0 : undefined,
                  transform:   `translateY(${shiftY}px)`,
                  transition:  isDragging ? 'transform 0.18s ease' : 'top 0.22s ease, transform 0.18s ease',
                }}
                onPointerDown={e => handleDragHandlePointerDown(e, item.id)}
                onMouseEnter={onLeaveName}
                title="드래그하여 순서 변경"
              >
                <svg width="10" height="14" viewBox="0 0 10 14" fill="currentColor">
                  <circle cx="2.5" cy="2.5"  r="1.5"/>
                  <circle cx="7.5" cy="2.5"  r="1.5"/>
                  <circle cx="2.5" cy="7"    r="1.5"/>
                  <circle cx="7.5" cy="7"    r="1.5"/>
                  <circle cx="2.5" cy="11.5" r="1.5"/>
                  <circle cx="7.5" cy="11.5" r="1.5"/>
                </svg>
              </div>
            )}

            {/* 전체화면 / 복원 버튼: 드래그 핸들 위에 배치 */}
            {(onExpand || (isFocused && onRestore)) && (
              <button
                className={`pane-btn pane-btn-expand${isFocused ? ' active' : ''}`}
                style={{
                  position:   'fixed',
                  left:        screenX + 4,
                  top:         paneBottomY - 56,
                  zIndex:      504,
                  opacity:     isBeingDragged ? 0 : undefined,
                  transform:   `translateY(${shiftY}px)`,
                  transition:  shiftTrans,
                }}
                onClick={e => {
                  e.stopPropagation();
                  if (isFocused) onRestore?.();
                  else onExpand?.(item.id);
                }}
                title={isFocused ? '전체 보기로 복원' : '이 지표만 보기'}
              >
                {isFocused ? <IconRestore /> : <IconExpand />}
              </button>
            )}
          </React.Fragment>
        );
      })}

      {/* ── 드래그 중 오버레이 ─────────────────────────────────────────── */}
      {isDragging && draggingLayout && (() => {
        const ghostColor = 'rgba(122, 162, 247';
        const ghostTop = dragState.ghostCursorY - draggingLayout.height / 2;

        return (
          <>
            {/* ① 전체 지표 영역 dim — 고스트·shift 가 더 선명하게 보이도록 */}
            {paneItems.length > 0 && (() => {
              const firstL = paneItemLayout(paneItems[0], paneLayouts);
              const lastL  = paneItemLayout(paneItems[paneItems.length - 1], paneLayouts);
              if (!firstL || !lastL) return null;
              return (
                <div style={{
                  position: 'fixed',
                  left:   containerRect.left,
                  top:    containerRect.top + firstL.topY,
                  width:  containerRect.width,
                  height: lastL.topY + lastL.height - firstL.topY,
                  background: 'rgba(8, 10, 20, 0.38)',
                  zIndex: 478,
                  pointerEvents: 'none',
                }} />
              );
            })()}

            {/* ② 원본 위치 placeholder (파선 테두리) */}
            <div className="pane-drag-placeholder" style={{
              position: 'fixed',
              left:     containerRect.left,
              top:      containerRect.top + draggingLayout.topY,
              width:    containerRect.width,
              height:   draggingLayout.height,
              zIndex:   490,
            }}/>

            {/* ③ 드롭: 합치기(+) 또는 이동 안내선 */}
            {dragState.dropMode === 'merge' && mergeTargetLayout ? (
              <div
                className="pane-drop-merge"
                style={{
                  position: 'fixed',
                  left:     containerRect.left,
                  top:      containerRect.top + mergeTargetLayout.topY,
                  width:    containerRect.width,
                  height:   mergeTargetLayout.height,
                }}
              >
                <div className="pane-drop-merge-badge">
                  <IconMerge />
                  <span>합치기</span>
                </div>
              </div>
            ) : dropLineY !== null ? (
              <>
                <div className="pane-drop-line" style={{
                  top:   dropLineY,
                  left:  containerRect.left,
                  width: containerRect.width,
                }}/>
                <div
                  className="pane-drop-move-hint"
                  style={{
                    top:   dropLineY - 22,
                    left:  containerRect.left + 8,
                  }}
                >
                  이동
                </div>
              </>
            ) : null}

            {/* ④ 고스트: 캔버스 캡처 성공 → 실제 차트 이미지, 실패 → 색상 블록 */}
            {dragState.paneCapture ? (
              <div className="pane-drag-ghost-chart" style={{
                position: 'fixed',
                left:     containerRect.left,
                top:      ghostTop,
                width:    dragState.paneCapture.cssWidth,
                height:   draggingLayout.height,
              }}>
                <img
                  src={dragState.paneCapture.dataUrl}
                  style={{ width: '100%', height: '100%', display: 'block' }}
                  alt=""
                />
                <div className="pane-drag-ghost-label">
                  {dragState.ghostLabel}
                  <span className="pane-drag-ghost-mode">
                    {dragState.dropMode === 'merge' ? ' · 합치기' : ' · 이동'}
                  </span>
                </div>
              </div>
            ) : (
              /* 색상 블록 고스트 */
              <div className="pane-drag-ghost-block" style={{
                position:       'fixed',
                left:           containerRect.left,
                top:            ghostTop,
                width:          containerRect.width,
                height:         draggingLayout.height,
                background:     ghostColor + ', 0.20)',
                zIndex:         700,
                pointerEvents:  'none',
                border:         '2px solid rgba(122, 162, 247, 0.7)',
                borderRadius:   3,
                boxShadow:      '0 8px 32px rgba(0,0,0,0.6)',
                display:        'flex',
                alignItems:     'center',
                justifyContent: 'center',
              }}>
                <span style={{
                  color: '#fff', fontSize: 13, fontWeight: 700,
                  textShadow: '0 1px 4px rgba(0,0,0,0.7)',
                  pointerEvents: 'none', userSelect: 'none',
                }}>
                  {dragState.ghostLabel}
                  <span style={{ opacity: 0.75, fontWeight: 500, marginLeft: 6 }}>
                    {dragState.dropMode === 'merge' ? '합치기' : '이동'}
                  </span>
                </span>
              </div>
            )}
          </>
        );
      })()}
    </>
  );
};

function formatVal(v: number): string {
  const abs = Math.abs(v);
  if (abs >= 100_000) return v.toFixed(0);
  if (abs >= 1_000)   return v.toFixed(1);
  if (abs >= 1)       return v.toFixed(2);
  return v.toFixed(4);
}

export default PaneLegend;
