import React, { useMemo, useState, useCallback } from 'react';
import { View, Text } from 'react-native';
import { Canvas, Path, Skia, Line, vec } from '@shopify/react-native-skia';
import { Gesture, GestureDetector } from 'react-native-gesture-handler';
import { useSharedValue, runOnJS } from 'react-native-reanimated';
import { CHANNEL_COLORS } from '../protocol/constants';
import { useTheme } from '../design/tokens';
import type { WaveformData } from '../hooks/useWaveform';

interface Props {
  data:            WaveformData;
  visibleChannels: boolean[];
  width:           number;
  height:          number;
  logScale?:       boolean;
  interactive?:    boolean;
}

const PADDING = { top: 14, right: 14, bottom: 34, left: 56 };
const LOG_Y_TICKS = [1e-1, 1e0, 1e1, 1e2, 1e3, 1e4, 1e5, 1e6, 1e7];
const X_TICK_COUNT = 5;

function niceStep(range: number, targetCount: number): number {
  const rough = range / targetCount;
  const mag   = Math.pow(10, Math.floor(Math.log10(rough)));
  for (const f of [1, 2, 2.5, 5, 10]) {
    if (f * mag >= rough) return f * mag;
  }
  return 10 * mag;
}

function xTicks(minMs: number, maxMs: number): number[] {
  const range = maxMs - minMs;
  if (range <= 0) return [];
  const step  = niceStep(range, X_TICK_COUNT);
  const start = Math.ceil(minMs / step) * step;
  const ticks: number[] = [];
  for (let t = start; t <= maxMs * 1.001; t += step) ticks.push(t);
  return ticks;
}

function linYTicks(minUV: number, maxUV: number): number[] {
  const range  = maxUV - minUV;
  const step   = niceStep(range, 6);
  const start  = Math.ceil(minUV / step) * step;
  const ticks: number[] = [];
  for (let v = start; v <= maxUV * 1.001; v += step) ticks.push(v);
  return ticks;
}

function formatUVShort(uv: number): string {
  const abs = Math.abs(uv);
  if (abs === 0) return '0';
  if (abs >= 1e6) return `${(uv / 1e6).toFixed(1)}V`;
  if (abs >= 1e3) return `${(uv / 1e3).toFixed(1)}mV`;
  if (abs >= 1)   return `${uv.toFixed(0)}μV`;
  if (abs >= 1e-3) return `${(uv * 1e3).toFixed(1)}nV`;
  return uv.toExponential(1);
}

function formatLogTick(v: number): string {
  if (v >= 1000) return `${v / 1000}k`;
  return `${v}`;
}

function formatMsShort(ms: number): string {
  if (ms >= 1000) return `${(ms / 1000).toFixed(1)}s`;
  if (ms >= 1)    return `${ms % 1 === 0 ? ms : ms.toFixed(1)}ms`;
  return `${(ms * 1000).toFixed(0)}μs`;
}

export function WaveformChart({ data, visibleChannels, width, height, logScale = true, interactive = false }: Props) {
  const theme = useTheme();
  const C = theme.chart;
  const plotW = width  - PADDING.left - PADDING.right;
  const plotH = height - PADDING.top  - PADDING.bottom;

  // ── Zoom & pan state (JS thread for rendering) ───────────────────────────
  const [view, setView] = useState({ sx: 1, sy: 1, ox: 0, oy: 0 });
  const { sx: scaleX, sy: scaleY, ox: offsetX, oy: offsetY } = view;

  // ── Shared values for gesture worklets ───────────────────────────────────
  const savedOx = useSharedValue(0);
  const savedOy = useSharedValue(0);
  const savedSx = useSharedValue(1);
  const savedSy = useSharedValue(1);
  const curSx   = useSharedValue(1);

  const applyView = useCallback((sx: number, sy: number, ox: number, oy: number) => {
    setView({ sx, sy, ox, oy });
  }, []);

  const resetView = useCallback(() => {
    savedOx.value = 0; savedOy.value = 0;
    savedSx.value = 1; savedSy.value = 1;
    curSx.value   = 1;
    setView({ sx: 1, sy: 1, ox: 0, oy: 0 });
  }, []);

  // ── Gestures ─────────────────────────────────────────────────────────────
  const panGesture = Gesture.Pan()
    .onStart(() => {
      'worklet';
      savedOx.value = savedOx.value + 0; // capture
      savedOy.value = savedOy.value + 0;
    })
    .onUpdate((e) => {
      'worklet';
      const ox = savedOx.value + e.translationX / curSx.value;
      const oy = savedOy.value + e.translationY / curSx.value;
      runOnJS(applyView)(curSx.value, curSx.value, ox, oy);
    })
    .onEnd(() => {
      'worklet';
    })
    .minPointers(1)
    .maxPointers(1);

  const pinchGesture = Gesture.Pinch()
    .onStart(() => {
      'worklet';
      savedSx.value = curSx.value;
    })
    .onUpdate((e) => {
      'worklet';
      const ns = Math.max(0.5, Math.min(savedSx.value * e.scale, 50));
      curSx.value = ns;
      runOnJS(applyView)(ns, ns, savedOx.value, savedOy.value);
    });

  const doubleTapGesture = Gesture.Tap()
    .numberOfTaps(2)
    .onEnd(() => {
      'worklet';
      runOnJS(resetView)();
    });

  // Sync shared values when JS state settles (after pan end)
  const panEndGesture = Gesture.Pan()
    .onEnd((e) => {
      'worklet';
      savedOx.value = savedOx.value + e.translationX / curSx.value;
      savedOy.value = savedOy.value + e.translationY / curSx.value;
    })
    .minPointers(1)
    .maxPointers(1);

  const composed = Gesture.Simultaneous(
    Gesture.Simultaneous(panGesture, panEndGesture),
    pinchGesture,
    doubleTapGesture,
  );

  // ── Visible data range ───────────────────────────────────────────────────
  const totalTimeMs = data.totalTimeMs;
  const visibleTimeCenterMs = totalTimeMs / 2 - offsetX / plotW * totalTimeMs;
  const visibleTimeSpanMs   = totalTimeMs / scaleX;
  const xMinMs = Math.max(0, visibleTimeCenterMs - visibleTimeSpanMs / 2);
  const xMaxMs = Math.min(totalTimeMs, visibleTimeCenterMs + visibleTimeSpanMs / 2);

  // ── Y mapping ────────────────────────────────────────────────────────────
  const yLogMin   = data.minLogUV - 0.5;
  const yLogMax   = data.maxLogUV + 0.5;

  const baseYMin = data.minUV;
  const baseYMax = data.maxUV;
  const baseYRange = baseYMax - baseYMin;
  const yCenter = (baseYMin + baseYMax) / 2 - (offsetY / plotH) * baseYRange;
  const ySpan   = baseYRange / scaleY;
  const yLinMin = yCenter - ySpan / 2;
  const yLinMax = yCenter + ySpan / 2;
  const yLinRange = Math.max(yLinMax - yLinMin, 1e-9);

  const yLogRange = yLogMax - yLogMin;

  const toY = useMemo(() => {
    if (logScale) {
      return (uv: number): number => {
        const absV = Math.abs(uv);
        if (absV < 1e-12) return PADDING.top + plotH;
        const logV = Math.log10(absV);
        return PADDING.top + plotH - ((logV - yLogMin) / yLogRange) * plotH;
      };
    }
    return (uv: number): number =>
      PADDING.top + plotH - ((uv - yLinMin) / yLinRange) * plotH;
  }, [logScale, yLogMin, yLogRange, yLinMin, yLinRange, plotH]);

  // ── X mapping ────────────────────────────────────────────────────────────
  const toX = (ms: number): number =>
    PADDING.left + ((ms - xMinMs) / Math.max(xMaxMs - xMinMs, 1e-9)) * plotW;

  // ── Grid / tick values ──────────────────────────────────────────────────
  const yGridValues = useMemo(() => {
    if (logScale) {
      return LOG_Y_TICKS.filter(v => {
        const log = Math.log10(v);
        return log >= yLogMin && log <= yLogMax;
      });
    }
    return linYTicks(yLinMin, yLinMax);
  }, [logScale, yLogMin, yLogMax, yLinMin, yLinMax]);

  const xTickValues = useMemo(() => xTicks(xMinMs, xMaxMs), [xMinMs, xMaxMs]);

  // ── Channel paths ────────────────────────────────────────────────────────
  const channelPaths = useMemo(() => {
    return data.channels.map((ch, idx) => {
      if (!visibleChannels[idx]) return null;
      const path = Skia.Path.Make();
      let moved = false;
      const timeStep = data.totalTimeMs / Math.max(ch.length - 1, 1);
      for (let i = 0; i < ch.length; i++) {
        const ms = i * timeStep;
        if (ms < xMinMs || ms > xMaxMs) continue;
        if (logScale && Math.abs(ch[i]) < 1e-12) continue;
        const x = toX(ms);
        const y = toY(ch[i]);
        if (!isFinite(y) || x < PADDING.left || x > PADDING.left + plotW) continue;
        if (!moved) { path.moveTo(x, y); moved = true; }
        else          path.lineTo(x, y);
      }
      return path;
    });
  }, [data, visibleChannels, toY, xMinMs, xMaxMs, plotW, plotH, logScale]);

  const top  = PADDING.top;
  const bot  = PADDING.top + plotH;
  const left = PADDING.left;
  const right = PADDING.left + plotW;

  const chartContent = (
      <View style={[{ backgroundColor: C.bg, width, height, position: 'relative' as const }]}>
        <Canvas style={{ width, height }}>

          {/* Y grid lines */}
          {yGridValues.map((v) => {
            const y = toY(v);
            if (!isFinite(y) || y < top - 1 || y > bot + 1) return null;
            return (
              <Line key={v} p1={vec(left, y)} p2={vec(right, y)}
                color={C.grid} strokeWidth={0.5} />
            );
          })}

          {/* X grid lines */}
          {xTickValues.map((ms) => {
            const x = toX(ms);
            if (x < left - 1 || x > right + 1) return null;
            return (
              <Line key={ms} p1={vec(x, top)} p2={vec(x, bot)}
                color={C.gridFine} strokeWidth={0.5} />
            );
          })}

          {/* Baseline */}
          {!logScale && (() => {
            const y0 = toY(0);
            return isFinite(y0) && y0 >= top && y0 <= bot ? (
              <Line p1={vec(left, y0)} p2={vec(right, y0)}
                color={C.axis} strokeWidth={0.8} />
            ) : null;
          })()}

          {/* X axis */}
          <Line p1={vec(left, bot)} p2={vec(right, bot)}
            color={C.axis} strokeWidth={1} />

          {/* Channel paths */}
          {channelPaths.map((path, idx) => {
            if (!path || !visibleChannels[idx]) return null;
            return (
              <Path key={idx} path={path}
                color={CHANNEL_COLORS[idx]}
                style="stroke" strokeWidth={interactive ? 2.5 : 1.5}
                strokeJoin="round" strokeCap="round" />
            );
          })}
        </Canvas>

        {/* Y-axis labels */}
        {yGridValues.map((v) => {
          const y = toY(v);
          if (!isFinite(y) || y < top - 1 || y > bot + 1) return null;
          const label = logScale ? formatLogTick(v) : formatUVShort(v);
          return (
            <Text key={v}
              style={{ position: 'absolute', top: y - 7, left: 2, width: PADDING.left - 4, color: C.label, fontSize: 9, textAlign: 'right' }}>
              {label}
            </Text>
          );
        })}

        {/* X-axis tick labels */}
        {xTickValues.map((ms) => {
          const x = toX(ms);
          if (x < left - 10 || x > right + 10) return null;
          return (
            <Text key={ms}
              style={{ position: 'absolute', top: height - 20, left: x - 18, width: 36, color: C.label, fontSize: 9, textAlign: 'center' }}>
              {formatMsShort(ms)}
            </Text>
          );
        })}

        {/* Axis unit labels */}
        <Text style={{ position: 'absolute', top: 2, left: 2, color: C.labelDim, fontSize: 9 }}>μV</Text>
        <Text style={{ position: 'absolute', top: height - 14, left: PADDING.left + plotW - 12, color: C.labelDim, fontSize: 9 }}>
          {data.totalTimeMs >= 1000 ? 's' : 'ms'}
        </Text>

        {/* Scale mode badge */}
        <Text style={{ position: 'absolute', top: 2, right: 4, color: C.badge, fontSize: 8, fontWeight: '700', letterSpacing: 0.5 }}>
          {logScale ? 'LOG' : 'LIN'}
        </Text>

        {/* Zoom indicator */}
        {(scaleX > 1.01 || scaleY > 1.01) && (
          <Text style={{ position: 'absolute', top: 2, right: 36, color: C.zoomBadge, fontSize: 8, fontWeight: '700' }}>
            ×{scaleX.toFixed(1)}
          </Text>
        )}
      </View>
  );

  if (interactive) {
    return <GestureDetector gesture={composed}>{chartContent}</GestureDetector>;
  }
  return chartContent;
}

