/**
 * PaneLegend
 * 각 서브 pane 좌측 상단에 인디케이터 명칭 + 현재 크로스헤어 값 표시.
 * 드래그 핸들(⠿) / 전체화면 / 닫기 버튼 포함.
 * 레이아웃 갱신 시 top transition 을 쓰지 않음 — 설정 변경 직후 라벨 이중 표시 방지.
 *
 * ─── 패인 인덱스 구조 ───────────────────────────────────────────────────────
 *  Pane 0  : 메인 캔들차트
 *  Pane 1  : 볼륨 (항상 고정, ChartManager._getNextSubPane 이 2부터 시작하는 이유)
 *  Pane 2+ : 보조지표 (RSI=2, MACD=3, ...)
 * ────────────────────────────────────────────────────────────────────────────
 */
import React, { useState, useEffect, useCallback, useRef } from 'react';
import { createPortal } from 'react-dom';
import type { MouseEventParams, Time } from 'lightweight-charts';
import type { IndicatorConfig } from '../types';
import { ChartManager } from '../utils/ChartManager';
import { bindWindowDrag, clientXYFromReact } from '../utils/pointerDrag';
import { getPaneHostId } from '../utils/indicatorPaneMerge';
import {
  buildPaneItems,
  resolvePaneItemLayout,
  type PaneItem,
} from './paneLegendLayout';

// ── 타입 ─────────────────────────────────────────────────────────────────────
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;
  /** 지표 파라미터 동기화 중 — 잘못된 좌표로 라벨이 겹쳐 보이는 것 방지 */
  paused?:        boolean;
  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;
  /** 드래그 핸들 — ChartRightToolbar 에서 호출 */
  dragHandleRef?: React.MutableRefObject<((id: string, e: React.PointerEvent) => void) | null>;
  /** 크로스헤어 이동 시 보조지표 수치 표시 */
  crosshairInfoVisible?: boolean;
}

// ── 지그재그 배경색 — 홀짝 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;
}

// ── 아이콘 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 PaneLegend: React.FC<PaneLegendProps> = ({
  manager, indicators, containerEl, paused = false,
  onHoverName, onLeaveName, onDoubleClick,
  onReorder, onMerge, dragHandleRef,
  crosshairInfoVisible = true,
}) => {
  const [paneItems,   setPaneItems]   = useState<PaneItem[]>([]);
  const [paneLayouts, setPaneLayouts] = useState<Array<{ paneIndex: number; topY: number; height: number }>>([]);
  const [layoutEpoch, setLayoutEpoch] = useState(0);
  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 indicatorsRef  = useRef(indicators);
  const [portalHost, setPortalHost] = useState<HTMLElement | null>(null);

  /** React portal 전용 호스트 — chart-container 직접 조작·removeChild 경합 방지 */
  useEffect(() => {
    const sel = ':scope > .pane-legend-portal-host';
    let host = containerEl.querySelector<HTMLElement>(sel);
    if (!host) {
      host = document.createElement('div');
      host.className = 'pane-legend-portal-host';
      containerEl.appendChild(host);
    }
    setPortalHost(host);
    return () => { setPortalHost(null); };
  }, [containerEl]);

  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(() => { indicatorsRef.current  = indicators;  }, [indicators]);

  const syncPaneItems = useCallback(() => {
    if (!containerElRef.current?.isConnected) return;
    const nextItems = buildPaneItems(manager, indicators);
    setPaneLayouts(manager.getPaneLayouts());
    setPaneItems(nextItems);
    setLayoutEpoch(e => e + 1);
  }, [indicators, manager]);

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

  const scheduleRetry = useCallback(() => {
    syncPaneItems();
    retryRef.current.forEach(clearTimeout);
    retryRef.current = [
      setTimeout(syncPaneItems, 0),
      setTimeout(syncPaneItems, 50),
      setTimeout(syncPaneItems, 200),
      setTimeout(syncPaneItems, 500),
    ];
    requestAnimationFrame(() => {
      requestAnimationFrame(syncPaneItems);
    });
  }, [syncPaneItems]);

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

  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(() => {
    if (!crosshairInfoVisible) {
      setValues({});
      return;
    }
    const unsub = manager.subscribeCrosshair((p: MouseEventParams<Time>) => {
      if (!p.time) { setValues({}); return; }
      setValues(manager.getIndicatorValuesByIdFromParams(p));
    });
    return unsub;
  }, [manager, crosshairInfoVisible]);

  // ── 드롭 라인 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 = resolvePaneItemLayout(manager, items[0]);
      return pl ? rect.top + pl.topY : null;
    }
    if (idx >= items.length) {
      const pl = resolvePaneItemLayout(manager, items[items.length - 1]);
      return pl ? rect.top + pl.topY + pl.height : null;
    }
    const pl = resolvePaneItemLayout(manager, items[idx - 1]);
    return pl ? rect.top + pl.topY + pl.height : null;
  }, [manager]);

  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     = resolvePaneItemLayout(manager, item);
    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 = resolvePaneItemLayout(manager, item);
          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 = resolvePaneItemLayout(manager, items[i]);
            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);
  }, [manager]);

  useEffect(() => {
    if (!dragHandleRef) return;
    dragHandleRef.current = (id, e) => handleDragHandlePointerDown(e, id);
    return () => { dragHandleRef.current = null; };
  }, [dragHandleRef, handleDragHandlePointerDown]);

  // ── 렌더 ─────────────────────────────────────────────────────────────────
  const isDragging       = dragState !== null;
  const dropLineY        = isDragging && dragState!.dropMode === 'reorder'
    ? getDropLineY(dragState!.dropBeforeIndex)
    : null;
  const containerRect    = containerEl.getBoundingClientRect();
  void layoutEpoch;

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

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

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

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

  if (paused || !portalHost) return null;

  const layer = (
    <div className="pane-legend-layer" aria-hidden={paneItems.length === 0}>
      {paneItems.map((item, itemIdx) => {
        const pl = resolvePaneItemLayout(manager, item);
        if (!pl) return null;

        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 hoverScreenX   = containerRect.left;
        const hoverScreenY   = containerRect.top + pl.topY;
        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 shiftTrans = isDragging ? 'transform 0.18s ease' : undefined;

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

            {/* ── 레전드 레이블 (pane 좌측 상단) ─────────────────────── */}
            <div
              className="pane-legend-item"
              style={{
                position:     'absolute',
                left:          6,
                top:           pl.topY + 5 + stackIdx * 22,
                zIndex:        502,
                pointerEvents: isDragging ? 'none' : 'auto',
                display:       'flex',
                alignItems:    'center',
                gap:           '4px',
                opacity:       isBeingDragged ? 0 : 1,
                transform:     shiftY ? `translateY(${shiftY}px)` : undefined,
                transition:    shiftTrans,
              }}
            >
              <span
                className={`pane-legend-name${isPaneHidden ? ' pane-legend-name--hidden' : ''}`}
                onMouseEnter={() => !isDragging && onHoverName(item.id, hoverScreenX, hoverScreenY)}
                onMouseLeave={() => !isDragging && onLeaveName()}
                onDoubleClick={() => !isDragging && onDoubleClick?.(item.id)}
              >
                {item.label}
              </span>
              {crosshairInfoVisible && 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>
          </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 = resolvePaneItemLayout(manager, paneItems[0]);
              const lastL  = resolvePaneItemLayout(manager, paneItems[paneItems.length - 1]);
              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>
            )}
          </>
        );
      })()}
    </div>
  );

  return createPortal(layer, portalHost);
};

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;
