import { useEffect, useMemo, useRef, useState } from 'react';
import type { Candle, ChartIndicators } from '../../lib/api';
import type { TickerItem } from '../tickerbar/TickerBar';
import Card from '../../ui/Card';
import ChartLayersPanel from './ChartLayersPanel';
import ChartSideToolbar from './ChartSideToolbar';
import ChartToolbar from './ChartToolbar';
import TradingChart from './TradingChart';
import type { FibAnchor, FibRetracement } from './FibRetracementPrimitive';
import { LineStyle, type IChartApi, type ISeriesApi, type UTCTimestamp } from 'lightweight-charts';
import type { OverlayLayer } from './ChartPanel.types';

type Props = {
  candles: Candle[];
  indicators: ChartIndicators;
  dlobQuotes?: { bid: number | null; ask: number | null; mid: number | null } | null;
  tickerItems: TickerItem[];
  theme: 'day' | 'night';
  timeframe: string;
  bucketSeconds: number;
  seriesKey: string;
  onTimeframeChange: (tf: string) => void;
  showIndicators: boolean;
  onToggleIndicators: () => void;
  showBuild: boolean;
  onToggleBuild: () => void;
  onResetViewReady?: ((handler: (() => void) | null) => void) | undefined;
  seriesLabel: string;
  hasMoreHistory?: boolean;
  onRequestHistoryBefore?: (beforeTime: number) => Promise<void> | void;
};

type FibDragMode = 'move' | 'edit-b';

type FibDrag = {
  pointerId: number;
  mode: FibDragMode;
  startClientX: number;
  startClientY: number;
  start: FibAnchor;
  origin: FibRetracement;
  moved: boolean;
};

function isEditableTarget(t: EventTarget | null): boolean {
  if (!(t instanceof HTMLElement)) return false;
  const tag = t.tagName;
  if (tag === 'INPUT' || tag === 'TEXTAREA' || tag === 'SELECT') return true;
  return t.isContentEditable;
}

export default function ChartPanel({
  candles,
  indicators,
  dlobQuotes,
  tickerItems,
  theme,
  timeframe,
  bucketSeconds,
  seriesKey,
  onTimeframeChange,
  showIndicators,
  onToggleIndicators,
  showBuild,
  onToggleBuild,
  onResetViewReady,
  seriesLabel,
  hasMoreHistory = false,
  onRequestHistoryBefore,
}: Props) {
  const [isFullscreen, setIsFullscreen] = useState(false);
  const [activeTool, setActiveTool] = useState<'cursor' | 'fib-retracement'>('cursor');
  const [fibStart, setFibStart] = useState<FibAnchor | null>(null);
  const [fib, setFib] = useState<FibRetracement | null>(null);
  const [fibDraft, setFibDraft] = useState<FibRetracement | null>(null);
  const [layers, setLayers] = useState<OverlayLayer[]>([
    { id: 'dlob-quotes', name: 'DLOB Quotes', visible: true, locked: false, opacity: 0.9 },
    { id: 'drawings', name: 'Drawings', visible: true, locked: false, opacity: 1 },
  ]);
  const [layersOpen, setLayersOpen] = useState(false);
  const [fibVisible, setFibVisible] = useState(true);
  const [fibLocked, setFibLocked] = useState(false);
  const [fibOpacity, setFibOpacity] = useState(1);
  const [selectedOverlayId, setSelectedOverlayId] = useState<string | null>(null);
  const [priceAutoScale, setPriceAutoScale] = useState(true);

  const chartApiRef = useRef<IChartApi | null>(null);
  const candleSeriesRef = useRef<ISeriesApi<'Candlestick', UTCTimestamp> | null>(null);
  const activeToolRef = useRef(activeTool);
  const fibStartRef = useRef<FibAnchor | null>(fibStart);
  const pendingMoveRef = useRef<FibAnchor | null>(null);
  const pendingDragRef = useRef<{ anchor: FibAnchor; clientX: number; clientY: number } | null>(null);
  const rafRef = useRef<number | null>(null);
  const spaceDownRef = useRef<boolean>(false);
  const dragRef = useRef<FibDrag | null>(null);
  const selectPointerRef = useRef<number | null>(null);
  const selectedOverlayIdRef = useRef<string | null>(selectedOverlayId);
  const fibRef = useRef<FibRetracement | null>(fib);
  const resetChartViewRef = useRef<() => void>(() => {});

  useEffect(() => {
    if (!isFullscreen) return;
    const onKeyDown = (e: KeyboardEvent) => {
      if (e.key === 'Escape') setIsFullscreen(false);
    };
    window.addEventListener('keydown', onKeyDown);
    return () => window.removeEventListener('keydown', onKeyDown);
  }, [isFullscreen]);

  useEffect(() => {
    document.body.classList.toggle('chartFullscreen', isFullscreen);
    return () => document.body.classList.remove('chartFullscreen');
  }, [isFullscreen]);

  const cardClassName = useMemo(() => {
    return ['chartCard', isFullscreen ? 'chartCard--fullscreen' : null].filter(Boolean).join(' ');
  }, [isFullscreen]);

  useEffect(() => {
    activeToolRef.current = activeTool;
    if (activeTool !== 'fib-retracement') {
      setFibStart(null);
      setFibDraft(null);
    }
  }, [activeTool]);

  useEffect(() => {
    fibStartRef.current = fibStart;
  }, [fibStart]);

  useEffect(() => {
    selectedOverlayIdRef.current = selectedOverlayId;
  }, [selectedOverlayId]);

  useEffect(() => {
    fibRef.current = fib;
  }, [fib]);

  useEffect(() => {
    const onKeyDown = (e: KeyboardEvent) => {
      if (isEditableTarget(e.target)) return;

      if (e.altKey && !e.ctrlKey && !e.shiftKey && !e.metaKey && e.key.toLowerCase() === 'r') {
        e.preventDefault();
        resetChartViewRef.current();
        return;
      }

      if (e.code === 'Space') {
        spaceDownRef.current = true;
        e.preventDefault();
        return;
      }

      if (e.key === 'Escape') {
        if (dragRef.current) {
          dragRef.current = null;
          pendingDragRef.current = null;
          selectPointerRef.current = null;
          setFibDraft(null);
          return;
        }

        if (activeToolRef.current === 'fib-retracement') {
          setFibStart(null);
          setFibDraft(null);
          setActiveTool('cursor');
          return;
        }

        if (selectedOverlayIdRef.current) setSelectedOverlayId(null);
        return;
      }

      if (e.key === 'Delete' || e.key === 'Backspace') {
        if (selectedOverlayIdRef.current === 'fib') {
          clearFib();
        }
      }
    };

    const onKeyUp = (e: KeyboardEvent) => {
      if (e.code === 'Space') {
        spaceDownRef.current = false;
      }
    };

    window.addEventListener('keydown', onKeyDown);
    window.addEventListener('keyup', onKeyUp);
    return () => {
      window.removeEventListener('keydown', onKeyDown);
      window.removeEventListener('keyup', onKeyUp);
    };
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, []);

  useEffect(() => {
    return () => {
      if (rafRef.current != null) cancelAnimationFrame(rafRef.current);
    };
  }, []);

  function zoomTime(factor: number) {
    const chart = chartApiRef.current;
    if (!chart) return;
    const ts = chart.timeScale();
    const range = ts.getVisibleLogicalRange();
    if (!range) return;
    const from = range.from as number;
    const to = range.to as number;
    const span = Math.max(5, (to - from) * factor);
    const center = (from + to) / 2;
    ts.setVisibleLogicalRange({ from: center - span / 2, to: center + span / 2 });
  }

  function resetChartView() {
    const chart = chartApiRef.current;
    if (!chart) return;

    chart.timeScale().resetTimeScale();
    candleSeriesRef.current?.priceScale().setAutoScale(true);
    setPriceAutoScale(true);
  }

  resetChartViewRef.current = resetChartView;

  function clamp01(v: number): number {
    if (!Number.isFinite(v)) return 1;
    return Math.max(0, Math.min(1, v));
  }

  const quotesLayer = useMemo(() => layers.find((l) => l.id === 'dlob-quotes'), [layers]);
  const quotesVisible = Boolean(quotesLayer?.visible);
  const quotesOpacity = clamp01(quotesLayer?.opacity ?? 1);

  const priceLines = useMemo(() => {
    if (!quotesVisible) return [];
    return [
      {
        id: 'dlob-bid',
        title: 'DLOB Bid',
        price: dlobQuotes?.bid ?? null,
        color: `rgba(34,197,94,${quotesOpacity})`,
        lineStyle: LineStyle.Dotted,
      },
      {
        id: 'dlob-mid',
        title: 'DLOB Mid',
        price: dlobQuotes?.mid ?? null,
        color: `rgba(230,233,239,${quotesOpacity})`,
        lineStyle: LineStyle.Dashed,
      },
      {
        id: 'dlob-ask',
        title: 'DLOB Ask',
        price: dlobQuotes?.ask ?? null,
        color: `rgba(239,68,68,${quotesOpacity})`,
        lineStyle: LineStyle.Dotted,
      },
    ];
  }, [dlobQuotes?.ask, dlobQuotes?.bid, dlobQuotes?.mid, quotesOpacity, quotesVisible, theme]);

  function updateLayer(layerId: string, patch: Partial<OverlayLayer>) {
    setLayers((prev) => prev.map((l) => (l.id === layerId ? { ...l, ...patch } : l)));
  }

  function clearFib() {
    setFib(null);
    setFibStart(null);
    setFibDraft(null);
    dragRef.current = null;
    pendingDragRef.current = null;
    selectPointerRef.current = null;
    setSelectedOverlayId(null);
  }

  function computeFibFromDrag(drag: FibDrag, pointer: FibAnchor): FibRetracement {
    if (drag.mode === 'edit-b') return { a: drag.origin.a, b: pointer };
    const deltaLogical = pointer.logical - drag.start.logical;
    const deltaPrice = pointer.price - drag.start.price;
    return {
      a: { logical: drag.origin.a.logical + deltaLogical, price: drag.origin.a.price + deltaPrice },
      b: { logical: drag.origin.b.logical + deltaLogical, price: drag.origin.b.price + deltaPrice },
    };
  }

  function scheduleFrame() {
    if (rafRef.current != null) return;
    rafRef.current = window.requestAnimationFrame(() => {
      rafRef.current = null;

      const drag = dragRef.current;
      const pendingDrag = pendingDragRef.current;
      if (drag && pendingDrag) {
        if (!drag.moved) {
          const dx = pendingDrag.clientX - drag.startClientX;
          const dy = pendingDrag.clientY - drag.startClientY;
          if (dx * dx + dy * dy >= 16) drag.moved = true; // ~4px threshold
        }
        if (drag.moved) {
          setFibDraft(computeFibFromDrag(drag, pendingDrag.anchor));
        }
        return;
      }

      const pointer = pendingMoveRef.current;
      if (!pointer) return;
      if (activeToolRef.current !== 'fib-retracement') return;

      const start2 = fibStartRef.current;
      if (!start2) return;
      setFibDraft({ a: start2, b: pointer });
    });
  }

  const drawingsLayer =
    layers.find((l) => l.id === 'drawings') ?? { id: 'drawings', name: 'Drawings', visible: true, locked: false, opacity: 1 };
  const fibEffectiveVisible = fibVisible && drawingsLayer.visible;
  const fibEffectiveOpacity = fibOpacity * drawingsLayer.opacity;
  const fibEffectiveLocked = fibLocked || drawingsLayer.locked;
  const fibSelected = selectedOverlayId === 'fib';
  const fibRenderable = fibEffectiveVisible ? (fibDraft ?? fib) : null;

  useEffect(() => {
    if (selectedOverlayId !== 'fib') return;
    if (!fib) {
      setSelectedOverlayId(null);
      return;
    }
    if (!fibEffectiveVisible) setSelectedOverlayId(null);
  }, [fib, fibEffectiveVisible, selectedOverlayId]);

  useEffect(() => {
    if (!onResetViewReady) return;
    onResetViewReady(() => resetChartViewRef.current());
    return () => onResetViewReady(null);
  }, [onResetViewReady]);

  return (
    <>
      {isFullscreen ? <div className="chartBackdrop" onClick={() => setIsFullscreen(false)} /> : null}
      <Card className={cardClassName}>
        <div className="chartCard__toolbar">
          <ChartToolbar
            tickerItems={tickerItems}
            timeframe={timeframe}
            onTimeframeChange={onTimeframeChange}
            showIndicators={showIndicators}
            onToggleIndicators={onToggleIndicators}
            showBuild={showBuild}
            onToggleBuild={onToggleBuild}
            priceAutoScale={priceAutoScale}
            onTogglePriceAutoScale={() => setPriceAutoScale((v) => !v)}
            seriesLabel={seriesLabel}
            isFullscreen={isFullscreen}
            onToggleFullscreen={() => setIsFullscreen((v) => !v)}
          />
        </div>
        <div className="chartCard__content">
          <ChartSideToolbar
            timeframe={timeframe}
            activeTool={activeTool}
            hasFib={fib != null || fibDraft != null}
            isLayersOpen={layersOpen}
            onToolChange={setActiveTool}
            onToggleLayers={() => setLayersOpen((v) => !v)}
            onZoomIn={() => zoomTime(0.8)}
            onZoomOut={() => zoomTime(1.25)}
            onResetView={resetChartView}
            onClearFib={clearFib}
          />
          <div className="chartCard__chart">
            <TradingChart
              candles={candles}
              theme={theme}
              oracle={indicators.oracle}
              sma20={indicators.sma20}
              ema20={indicators.ema20}
              bb20={indicators.bb20}
              showIndicators={showIndicators}
              showBuild={showBuild}
              bucketSeconds={bucketSeconds}
              seriesKey={seriesKey}
              priceLines={priceLines}
              fib={fibRenderable}
              fibOpacity={fibEffectiveOpacity}
              fibSelected={fibSelected}
              priceAutoScale={priceAutoScale}
              hasMoreHistory={hasMoreHistory}
              onRequestHistoryBefore={onRequestHistoryBefore}
              onReady={({ chart, candles: candleSeries }) => {
                chartApiRef.current = chart;
                candleSeriesRef.current = candleSeries;
              }}
              onChartClick={(p) => {
                if (activeTool === 'fib-retracement') {
                  if (!fibStartRef.current) {
                    fibStartRef.current = p;
                    setFibStart(p);
                    setFibDraft({ a: p, b: p });
                    return;
                  }
                  setFib({ a: fibStartRef.current, b: p });
                  setFibStart(null);
                  fibStartRef.current = null;
                  setFibDraft(null);
                  setActiveTool('cursor');
                  return;
                }

                if (p.target === 'chart') setSelectedOverlayId(null);
              }}
              onChartCrosshairMove={(p) => {
                pendingMoveRef.current = p;
                scheduleFrame();
              }}
              onPointerEvent={({ type, logical, price, target, event }) => {
                const pointer: FibAnchor = { logical, price };

                if (type === 'pointerdown') {
                  if (event.button !== 0) return;
                  if (spaceDownRef.current) return;
                  if (activeToolRef.current !== 'cursor') return;
                  if (target !== 'fib') return;
                  if (!fibRef.current) return;
                  if (!fibEffectiveVisible) return;

                  if (selectedOverlayIdRef.current !== 'fib') {
                    setSelectedOverlayId('fib');
                    selectPointerRef.current = event.pointerId;
                    return { consume: true, capturePointer: true };
                  }

                  if (fibEffectiveLocked) {
                    selectPointerRef.current = event.pointerId;
                    return { consume: true, capturePointer: true };
                  }

                  dragRef.current = {
                    pointerId: event.pointerId,
                    mode: event.ctrlKey ? 'edit-b' : 'move',
                    startClientX: event.clientX,
                    startClientY: event.clientY,
                    start: pointer,
                    origin: fibRef.current,
                    moved: false,
                  };
                  pendingDragRef.current = { anchor: pointer, clientX: event.clientX, clientY: event.clientY };
                  setFibDraft(fibRef.current);
                  return { consume: true, capturePointer: true };
                }

                const drag = dragRef.current;
                if (drag && drag.pointerId === event.pointerId) {
                  if (type === 'pointermove') {
                    pendingDragRef.current = { anchor: pointer, clientX: event.clientX, clientY: event.clientY };
                    scheduleFrame();
                    return { consume: true };
                  }
                  if (type === 'pointerup' || type === 'pointercancel') {
                    if (drag.moved) setFib(computeFibFromDrag(drag, pointer));
                    dragRef.current = null;
                    pendingDragRef.current = null;
                    setFibDraft(null);
                    return { consume: true };
                  }
                  return;
                }

                if (selectPointerRef.current != null && selectPointerRef.current === event.pointerId) {
                  if (type === 'pointermove') return { consume: true };
                  if (type === 'pointerup' || type === 'pointercancel') {
                    selectPointerRef.current = null;
                    return { consume: true };
                  }
                }
              }}
            />

            <ChartLayersPanel
              open={layersOpen}
              layers={layers}
              onRequestClose={() => setLayersOpen(false)}
              onToggleLayerVisible={(layerId) => {
                const layer = layers.find((l) => l.id === layerId);
                if (!layer) return;
                updateLayer(layerId, { visible: !layer.visible });
              }}
              onToggleLayerLocked={(layerId) => {
                const layer = layers.find((l) => l.id === layerId);
                if (!layer) return;
                updateLayer(layerId, { locked: !layer.locked });
              }}
              onSetLayerOpacity={(layerId, opacity) => updateLayer(layerId, { opacity: clamp01(opacity) })}
              fibPresent={fib != null}
              fibSelected={fibSelected}
              fibVisible={fibVisible}
              fibLocked={fibLocked}
              fibOpacity={fibOpacity}
              onSelectFib={() => setSelectedOverlayId('fib')}
              onToggleFibVisible={() => setFibVisible((v) => !v)}
              onToggleFibLocked={() => setFibLocked((v) => !v)}
              onSetFibOpacity={(opacity) => setFibOpacity(clamp01(opacity))}
              onDeleteFib={clearFib}
            />
          </div>
        </div>
      </Card>
    </>
  );
}
