import React, { useState, useEffect, useMemo } from 'react';
import {
  View, Text, StyleSheet, TouchableOpacity, Modal, FlatList,
  ActivityIndicator, TextInput, useWindowDimensions, Platform, ScrollView,
} from 'react-native';
import { Canvas, Path, Skia, Line, vec } from '@shopify/react-native-skia';
import { useDataStore } from '../../src/stores/dataStore';
import { useDeviceStore } from '../../src/stores/deviceStore';
import * as StorageService from '../../src/services/StorageService';
import * as BinLoader from '../../src/services/BinLoader';
import type { MeasurementFrame } from '../../src/protocol/types';
import type { SessionInfo } from '../../src/services/StorageService';
import { CHANNEL_COLORS } from '../../src/protocol/constants';

// ── Constants ──────────────────────────────────────────────────────────────

const CP = { top: 14, right: 14, bottom: 34, left: 52 };
const CANVAS_H = 70;

const SAMPLE_FREQ_HZ: Record<number, number> = {
  0x00: 250000, 0x01: 125000, 0x02: 62500,  0x03: 31250,
  0x04: 15600,  0x05: 7800,   0x06: 3900,   0x07: 1950,
  0x08: 977,    0x09: 488,    0x0a: 244,    0x0b: 122,   0x0c: 61,
};

const JET_HEX: string[] = Array.from({ length: 256 }, (_, i) => {
  const t = i / 255;
  const r = Math.round(Math.max(0, Math.min(255, (1.5 - Math.abs(4 * t - 3)) * 255)));
  const g = Math.round(Math.max(0, Math.min(255, (1.5 - Math.abs(4 * t - 2)) * 255)));
  const b = Math.round(Math.max(0, Math.min(255, (1.5 - Math.abs(4 * t - 1)) * 255)));
  const h = (v: number) => v.toString(16).padStart(2, '0');
  return `#${h(r)}${h(g)}${h(b)}`;
});

// ── Gate Config ────────────────────────────────────────────────────────────

import type { GateConfig, GateSpacing } from '../../src/protocol/types';

function computeGatePositions(cfg: GateConfig, sampleDepth: number, hz: number): number[] {
  if (sampleDepth === 0 || hz === 0 || cfg.count <= 0 || cfg.tStart >= cfg.tEnd) return [];
  const totalUs = (sampleDepth / hz) * 1e6;
  return Array.from({ length: cfg.count }, (_, i) => {
    const t = cfg.count === 1 ? 0.5 : i / (cfg.count - 1);
    const tUs = cfg.spacing === 'log'
      ? cfg.tStart * Math.pow(cfg.tEnd / cfg.tStart, t)
      : cfg.tStart + t * (cfg.tEnd - cfg.tStart);
    return Math.max(0.001, Math.min(0.999, tUs / totalUs));
  });
}

function gateColor(idx: number, total: number): string {
  const t = total <= 1 ? 0.5 : idx / (total - 1);
  return JET_HEX[Math.round(t * 255)];
}

function fmtUs(us: number): string {
  if (us >= 1000) return `${(us / 1000).toFixed(2)}ms`;
  return `${us.toFixed(0)}μs`;
}

// ── Types ──────────────────────────────────────────────────────────────────

interface LoadedFrame {
  frameId:     number;
  adcUV:       Float64Array[];
  sampleDepth: number;
  channelNum:  number;
}

// ── Utility ────────────────────────────────────────────────────────────────

function niceStep(range: number, count: number): number {
  const rough = range / count;
  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 formatUVShort(uv: number): string {
  const a = Math.abs(uv);
  if (a >= 1e6) return `${(uv / 1e6).toFixed(1)}V`;
  if (a >= 1e3) return `${(uv / 1e3).toFixed(1)}mV`;
  if (a >= 1)   return `${uv.toFixed(0)}μV`;
  return `${(uv * 1e3).toFixed(1)}nV`;
}

// ── useProfileData ─────────────────────────────────────────────────────────

function useProfileData(sessionId: string, currentSessionId: string, history: MeasurementFrame[]) {
  const [histFrames, setHistFrames] = useState<LoadedFrame[]>([]);
  const [loading, setLoading]       = useState(false);
  const [progress, setProgress]     = useState({ current: 0, total: 0 });

  const liveFrames = useMemo<LoadedFrame[]>(() => {
    if (sessionId !== currentSessionId) return [];
    return [...history].reverse().map(f => ({
      frameId:     f.frameId,
      adcUV:       f.adcUV,
      sampleDepth: f.adcUV[0]?.length ?? 0,
      channelNum:  f.adcUV.length,
    }));
  }, [sessionId, currentSessionId, history]);

  useEffect(() => {
    if (sessionId === currentSessionId) { setHistFrames([]); return; }
    setLoading(true);
    setHistFrames([]);
    let cancelled = false;

    (async () => {
      const persisted = await StorageService.loadSessionFrames(sessionId);
      setProgress({ current: 0, total: persisted.length });
      const result: LoadedFrame[] = [];
      for (let i = 0; i < persisted.length; i++) {
        if (cancelled) return;
        const pf = persisted[i];
        if (pf.binPath) {
          const loaded = await BinLoader.loadBinFile(pf.binPath);
          if (loaded) result.push({ ...loaded, frameId: pf.frameId });
        }
        setProgress({ current: i + 1, total: persisted.length });
      }
      if (!cancelled) { setHistFrames(result); setLoading(false); }
    })();

    return () => { cancelled = true; };
  }, [sessionId, currentSessionId]);

  return {
    frames:  sessionId === currentSessionId ? liveFrames : histFrames,
    loading: sessionId !== currentSessionId && loading,
    progress,
  };
}

// ── GateSettingsModal ──────────────────────────────────────────────────────

function GateSettingsModal({ visible, config, onApply, onClose, sampleDepth, hz }: {
  visible: boolean;
  config: GateConfig;
  onApply: (cfg: GateConfig) => void;
  onClose: () => void;
  sampleDepth: number;
  hz: number;
}) {
  const [tStartStr, setTStartStr] = useState(String(config.tStart));
  const [tEndStr,   setTEndStr]   = useState(String(config.tEnd));
  const [countStr,  setCountStr]  = useState(String(config.count));
  const [spacing,   setSpacing]   = useState<GateSpacing>(config.spacing);

  useEffect(() => {
    if (visible) {
      setTStartStr(String(config.tStart));
      setTEndStr(String(config.tEnd));
      setCountStr(String(config.count));
      setSpacing(config.spacing);
    }
  }, [visible, config]);

  const maxUs = sampleDepth > 0 && hz > 0 ? (sampleDepth / hz) * 1e6 : 100000;

  const handleApply = () => {
    const tStart = Math.max(1, parseFloat(tStartStr) || config.tStart);
    const tEnd   = Math.min(Math.max(tStart + 1, parseFloat(tEndStr) || config.tEnd), maxUs * 0.999);
    const count  = Math.max(1, Math.min(64, parseInt(countStr, 10) || config.count));
    onApply({ tStart, tEnd, count, spacing });
    onClose();
  };

  return (
    <Modal visible={visible} transparent animationType="slide" onRequestClose={onClose}>
      <TouchableOpacity style={GSM.backdrop} activeOpacity={1} onPress={onClose} />
      <View style={GSM.sheet}>
        <Text style={GSM.title}>时间门配置</Text>

        <View style={GSM.row}>
          <View style={GSM.field}>
            <Text style={GSM.label}>起始时间 (μs)</Text>
            <TextInput
              style={GSM.input}
              keyboardType="numeric"
              value={tStartStr}
              onChangeText={setTStartStr}
            />
          </View>
          <View style={GSM.field}>
            <Text style={GSM.label}>终止时间 (μs)</Text>
            <TextInput
              style={GSM.input}
              keyboardType="numeric"
              value={tEndStr}
              onChangeText={setTEndStr}
            />
          </View>
        </View>

        <View style={[GSM.row, { marginTop: 6 }]}>
          <View style={GSM.field}>
            <Text style={GSM.label}>门数量</Text>
            <TextInput
              style={GSM.input}
              keyboardType="numeric"
              value={countStr}
              onChangeText={setCountStr}
            />
          </View>
          <View style={[GSM.field, { justifyContent: 'flex-end' }]}>
            <Text style={GSM.label}>间隔方式</Text>
            <View style={GSM.toggle}>
              {(['log', 'linear'] as GateSpacing[]).map(s => (
                <TouchableOpacity
                  key={s}
                  style={[GSM.toggleBtn, spacing === s && GSM.toggleBtnOn]}
                  onPress={() => setSpacing(s)}
                >
                  <Text style={[GSM.toggleTxt, spacing === s && GSM.toggleTxtOn]}>
                    {s === 'log' ? '对数' : '线性'}
                  </Text>
                </TouchableOpacity>
              ))}
            </View>
          </View>
        </View>

        {sampleDepth > 0 && hz > 0 && (
          <Text style={GSM.hint}>当前采样窗口: {fmtUs(maxUs)}</Text>
        )}

        <View style={GSM.actions}>
          <TouchableOpacity style={GSM.cancelBtn} onPress={onClose}>
            <Text style={GSM.cancelTxt}>取消</Text>
          </TouchableOpacity>
          <TouchableOpacity style={GSM.applyBtn} onPress={handleApply}>
            <Text style={GSM.applyTxt}>应用</Text>
          </TouchableOpacity>
        </View>
      </View>
    </Modal>
  );
}

const GSM = StyleSheet.create({
  backdrop:     { flex: 1, backgroundColor: 'rgba(0,0,0,0.6)' },
  sheet:        { backgroundColor: '#0e0e1c', borderTopLeftRadius: 20, borderTopRightRadius: 20, padding: 20, paddingBottom: 36 },
  title:        { color: '#8888bb', fontSize: 14, fontWeight: '700', marginBottom: 16 },
  row:          { flexDirection: 'row', gap: 12 },
  field:        { flex: 1, gap: 4 },
  label:        { color: '#4a4a6a', fontSize: 11, fontWeight: '600' },
  input:        { backgroundColor: '#111120', borderRadius: 8, borderWidth: 1, borderColor: '#2a2a3a', color: '#9090b8', fontSize: 13, paddingHorizontal: 10, paddingVertical: 8 },
  toggle:       { flexDirection: 'row', borderRadius: 8, overflow: 'hidden', borderWidth: 1, borderColor: '#2a2a3a' },
  toggleBtn:    { flex: 1, paddingVertical: 9, alignItems: 'center', backgroundColor: '#111120' },
  toggleBtnOn:  { backgroundColor: '#0d2040' },
  toggleTxt:    { color: '#4a4a6a', fontSize: 12, fontWeight: '700' },
  toggleTxtOn:  { color: '#4a9eff' },
  hint:         { color: '#2a2a4a', fontSize: 10, marginTop: 8 },
  actions:      { flexDirection: 'row', gap: 10, marginTop: 20 },
  cancelBtn:    { flex: 1, paddingVertical: 12, borderRadius: 10, borderWidth: 1, borderColor: '#2a2a3a', alignItems: 'center' },
  cancelTxt:    { color: '#4a4a6a', fontSize: 13, fontWeight: '600' },
  applyBtn:     { flex: 1, paddingVertical: 12, borderRadius: 10, backgroundColor: '#0d2040', borderWidth: 1, borderColor: '#4a9eff55', alignItems: 'center' },
  applyTxt:     { color: '#4a9eff', fontSize: 13, fontWeight: '700' },
});

// ── GatePreview ────────────────────────────────────────────────────────────

const GP = { t: 6, r: 8, b: 6, l: 8 };

function GatePreview({ refFrame, channelIdx, gatePositions, gateColors, width }: {
  refFrame: LoadedFrame | null;
  channelIdx: number;
  gatePositions: number[];
  gateColors: string[];
  width: number;
}) {
  const plotW = width - GP.l - GP.r;
  const plotH = CANVAS_H - GP.t - GP.b;

  const wavePath = useMemo(() => {
    if (!refFrame) return null;
    const ch = refFrame.adcUV[channelIdx];
    if (!ch || ch.length === 0) return null;
    const n = Math.min(ch.length, 256);
    let logMin = Infinity, logMax = -Infinity;
    for (let i = 0; i < n; i++) {
      const a = Math.abs(ch[Math.floor(i * ch.length / n)]);
      if (a > 1e-9) { const l = Math.log10(a); if (l < logMin) logMin = l; if (l > logMax) logMax = l; }
    }
    if (!isFinite(logMin)) return null;
    const logR = Math.max(logMax - logMin, 0.5);
    const path = Skia.Path.Make();
    let moved = false;
    for (let i = 0; i < n; i++) {
      const v = ch[Math.floor(i * ch.length / n)];
      const a = Math.abs(v);
      if (a < 1e-12) continue;
      const x = GP.l + (i / (n - 1)) * plotW;
      const y = GP.t + plotH - ((Math.log10(a) - logMin) / logR) * plotH;
      if (!isFinite(y)) continue;
      if (!moved) { path.moveTo(x, y); moved = true; } else path.lineTo(x, y);
    }
    return path;
  }, [refFrame, channelIdx, plotW, plotH]);

  return (
    <Canvas style={{ width, height: CANVAS_H }}>
      {wavePath && <Path path={wavePath} color="#445566" style="stroke" strokeWidth={1} />}
      {gatePositions.map((pos, gi) => (
        <Line key={gi}
          p1={vec(GP.l + pos * plotW, GP.t)}
          p2={vec(GP.l + pos * plotW, GP.t + plotH)}
          color={gateColors[gi] ?? '#fff'} strokeWidth={1.5}
        />
      ))}
    </Canvas>
  );
}

// ── GateLineChart ──────────────────────────────────────────────────────────

function GateLineChart({ frames, channelIdx, gatePositions, gateColors, logScale, width, height }: {
  frames: LoadedFrame[]; channelIdx: number;
  gatePositions: number[]; gateColors: string[];
  logScale: boolean; width: number; height: number;
}) {
  const plotW = width  - CP.left  - CP.right;
  const plotH = height - CP.top   - CP.bottom;

  const derived = useMemo(() => {
    const sd = frames[0]?.sampleDepth ?? 0;

    const series = gatePositions.map(pos => {
      if (sd === 0) return null;
      const si = Math.min(sd - 1, Math.floor(pos * sd));
      return frames.map(f => f.adcUV[channelIdx]?.[si] ?? 0);
    });

    let vMin = Infinity, vMax = -Infinity;
    for (const s of series) {
      if (!s) continue;
      for (const v of s) {
        if (Math.abs(v) < 1e-9) continue;
        const val = logScale ? Math.log10(Math.abs(v)) : v;
        if (val < vMin) vMin = val;
        if (val > vMax) vMax = val;
      }
    }
    if (!isFinite(vMin)) { vMin = logScale ? -3 : -1; vMax = logScale ? 6 : 1; }
    const pad = (vMax - vMin) * 0.05;
    const yMin = vMin - pad, yMax = vMax + pad;
    const yRange = Math.max(yMax - yMin, 1e-9);

    const toY = (v: number) => {
      const m = logScale ? (Math.abs(v) < 1e-12 ? yMin : Math.log10(Math.abs(v))) : v;
      return CP.top + plotH - ((m - yMin) / yRange) * plotH;
    };
    const toX = (i: number) => CP.left + (i / Math.max(frames.length - 1, 1)) * plotW;

    const paths = series.map((s) => {
      if (!s) return null;
      const path = Skia.Path.Make();
      let moved = false;
      for (let i = 0; i < s.length; i++) {
        if (logScale && Math.abs(s[i]) < 1e-12) continue;
        const x = toX(i), y = toY(s[i]);
        if (!isFinite(y)) continue;
        if (!moved) { path.moveTo(x, y); moved = true; } else path.lineTo(x, y);
      }
      return path;
    });

    let yTicks: number[];
    if (logScale) {
      yTicks = [-1, 0, 1, 2, 3, 4, 5, 6, 7]
        .filter(e => e >= yMin && e <= yMax)
        .map(e => Math.pow(10, e));
    } else {
      const step = niceStep(yMax - yMin, 5);
      yTicks = [];
      for (let v = Math.ceil(yMin / step) * step; v <= yMax * 1.001; v += step) yTicks.push(v);
    }

    const xTicks: number[] = [];
    if (frames.length > 2) {
      const step = Math.max(1, Math.round(niceStep(frames.length - 1, 5)));
      for (let xi = step; xi < frames.length; xi += step) xTicks.push(xi);
    }

    return { paths, yTicks, xTicks, toY, toX };
  }, [frames, channelIdx, gatePositions, logScale, plotW, plotH]);

  const { paths, yTicks, xTicks, toY, toX } = derived;
  const left = CP.left, right = CP.left + plotW;
  const top  = CP.top,  bot   = CP.top  + plotH;

  return (
    <View style={{ width, height, position: 'relative' }}>
      <Canvas style={{ width, height }}>
        {yTicks.map((v, idx) => {
          const y = toY(v);
          if (!isFinite(y) || y < top - 1 || y > bot + 1) return null;
          return <Line key={`y_${idx}`} p1={vec(left, y)} p2={vec(right, y)} color="#222" strokeWidth={0.5} />;
        })}
        {xTicks.map((i, idx) => (
          <Line key={`x_${idx}`} p1={vec(toX(i), top)} p2={vec(toX(i), bot)} color="#1e1e1e" strokeWidth={0.5} />
        ))}
        {!logScale && isFinite(toY(0)) && (
          <Line p1={vec(left, toY(0))} p2={vec(right, toY(0))} color="#444" strokeWidth={0.8} />
        )}
        <Line p1={vec(left, bot)}  p2={vec(right, bot)} color="#555" strokeWidth={1} />
        <Line p1={vec(left, top)}  p2={vec(left,  bot)} color="#555" strokeWidth={1} />
        {paths.map((p, gi) =>
          p ? <Path key={`g${gi}`} path={p} color={gateColors[gi] ?? '#fff'} style="stroke"
                strokeWidth={1.5} strokeJoin="round" strokeCap="round" /> : null,
        )}
      </Canvas>
      {yTicks.map((v, idx) => {
        const y = toY(v);
        if (!isFinite(y) || y < top - 1 || y > bot + 1) return null;
        const label = logScale ? (v >= 1000 ? `${v / 1000}k` : `${v}`) : formatUVShort(v);
        return <Text key={`yl_${idx}`} style={[LBL.y, { top: y - 7, left: 2, width: CP.left - 4 }]}>{label}</Text>;
      })}
      {xTicks.map((i, idx) => (
        <Text key={`xl_${idx}`} style={[LBL.x, { top: height - 20, left: toX(i) - 18, width: 36 }]}>{i}</Text>
      ))}
      <Text style={[LBL.y, { top: 2, left: 2 }]}>μV</Text>
      <Text style={[LBL.x, { position: 'absolute', bottom: 2, right: 8 }]}>帧</Text>
    </View>
  );
}

const LBL = StyleSheet.create({
  y: { position: 'absolute', color: '#666', fontSize: 9, textAlign: 'right' },
  x: { position: 'absolute', color: '#666', fontSize: 9, textAlign: 'center' },
});

// ── SessionPickerModal ─────────────────────────────────────────────────────

function SessionPickerModal({ visible, sessions, allSessions, currentId, selectedId, onSelect, onClose }: {
  visible: boolean; sessions: SessionInfo[]; allSessions: SessionInfo[];
  currentId: string; selectedId: string;
  onSelect: (id: string) => void; onClose: () => void;
}) {
  const [showAll, setShowAll] = useState(false);
  const source = showAll ? allSessions : sessions;

  const listData = useMemo(() => {
    const currentEntry = { sessionId: currentId, createdAt: 0, frameCount: -1, projectId: null };
    const rest = source.filter(s => s.sessionId !== currentId);
    return [currentEntry, ...rest];
  }, [source, currentId]);

  return (
    <Modal visible={visible} transparent animationType="slide" onRequestClose={onClose}>
      <TouchableOpacity style={SPM.backdrop} activeOpacity={1} onPress={onClose} />
      <View style={SPM.sheet}>
        <View style={SPM.header}>
          <Text style={SPM.title}>选择测线</Text>
          <TouchableOpacity style={[SPM.allBtn, showAll && SPM.allBtnOn]} onPress={() => setShowAll(v => !v)}>
            <Text style={[SPM.allTxt, showAll && SPM.allTxtOn]}>{showAll ? '全部' : '当前工程'}</Text>
          </TouchableOpacity>
        </View>
        <FlatList
          data={listData}
          keyExtractor={item => item.sessionId}
          style={SPM.list}
          renderItem={({ item }) => {
            const isCur = item.sessionId === currentId;
            const isSel = item.sessionId === selectedId;
            return (
              <TouchableOpacity
                style={[SPM.item, isSel && SPM.itemSel]}
                onPress={() => { onSelect(item.sessionId); onClose(); }}
              >
                <Text style={SPM.itemId} numberOfLines={1}>
                  {item.sessionId}{isCur ? '  (当前)' : ''}
                </Text>
                {item.frameCount >= 0 && (
                  <Text style={SPM.itemCnt}>{item.frameCount} 帧</Text>
                )}
              </TouchableOpacity>
            );
          }}
        />
      </View>
    </Modal>
  );
}

const SPM = StyleSheet.create({
  backdrop:  { flex: 1, backgroundColor: 'rgba(0,0,0,0.55)' },
  sheet:     { backgroundColor: '#0e0e1c', borderTopLeftRadius: 20, borderTopRightRadius: 20, padding: 16, maxHeight: '55%' },
  header:    { flexDirection: 'row', alignItems: 'center', marginBottom: 12, gap: 8 },
  title:     { flex: 1, color: '#6a6a8a', fontSize: 12, fontWeight: '700' },
  allBtn:    { paddingHorizontal: 10, paddingVertical: 4, borderRadius: 6, borderWidth: 1, borderColor: '#2a2a3a', backgroundColor: '#0e0e1c' },
  allBtnOn:  { borderColor: '#4a9eff55', backgroundColor: '#0d2040' },
  allTxt:    { color: '#555', fontSize: 10, fontWeight: '700' },
  allTxtOn:  { color: '#4a9eff' },
  list:      { maxHeight: 280 },
  item:      { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center', paddingVertical: 12, borderBottomWidth: 1, borderBottomColor: '#1a1a2a' },
  itemSel:   { backgroundColor: '#111130' },
  itemId:    { color: '#8888aa', fontSize: 12, fontFamily: Platform.OS === 'ios' ? 'Courier' : 'monospace', flex: 1 },
  itemCnt:   { color: '#4a4a6a', fontSize: 11, marginLeft: 8 },
});

// ── ProfileScreen ──────────────────────────────────────────────────────────

export default function ProfileScreen() {
  const { width: sw, height: sh } = useWindowDimensions();
  const { history, sessionId: currentSessionId, projectId } = useDataStore();
  const sampleFreqCode = useDeviceStore(s => s.config.sampleFreq);

  const gateConfig    = useDeviceStore(s => s.gateConfig);
  const setGateConfig = useDeviceStore(s => s.setGateConfig);

  const [channelIdx,      setChannelIdx]      = useState(0);
  const [sessionId,       setSessionId]       = useState(currentSessionId);
  const [projectSessions, setProjectSessions] = useState<SessionInfo[]>([]);
  const [allSessions,     setAllSessions]     = useState<SessionInfo[]>([]);
  const [pickerVisible,   setPickerVisible]   = useState(false);
  const [settingsVisible, setSettingsVisible] = useState(false);
  const [logScale,        setLogScale]        = useState(true);

  const { frames, loading, progress } = useProfileData(sessionId, currentSessionId, history);
  const CHART_H = Math.round(Math.min(300, sh * 0.42));

  useEffect(() => {
    const load = async () => {
      const all = await StorageService.listSessions();
      setAllSessions(all.filter(x => x.sessionId !== currentSessionId));
      if (projectId) {
        const proj = await StorageService.listSessionsByProject(projectId);
        setProjectSessions(proj.filter(x => x.sessionId !== currentSessionId));
      } else {
        setProjectSessions(all.filter(x => x.sessionId !== currentSessionId));
      }
    };
    load();
  }, [currentSessionId, projectId]);

  const maxCh = frames[0]?.channelNum ?? 6;
  useEffect(() => { if (channelIdx >= maxCh) setChannelIdx(0); }, [maxCh, channelIdx]);

  const hz = SAMPLE_FREQ_HZ[sampleFreqCode] ?? 31250;
  const sampleDepth = frames[0]?.sampleDepth ?? 0;

  const gatePositions = useMemo(
    () => computeGatePositions(gateConfig, sampleDepth, hz),
    [gateConfig, sampleDepth, hz],
  );
  const gateColors = useMemo(
    () => Array.from({ length: gateConfig.count }, (_, i) => gateColor(i, gateConfig.count)),
    [gateConfig.count],
  );

  const refFrame = useMemo(() => frames[Math.floor(frames.length / 2)] ?? null, [frames]);

  return (
    <View style={SCR.root}>

      {/* ── Header ── */}
      <View style={SCR.header}>
        <TouchableOpacity style={SCR.sessBtn} onPress={() => setPickerVisible(true)}>
          <Text style={SCR.sessBtnTxt} numberOfLines={1}>{sessionId}</Text>
          <Text style={SCR.sessBtnArrow}>▾</Text>
        </TouchableOpacity>
        <TouchableOpacity style={[SCR.scaleBtn, !logScale && SCR.scaleBtnOn]} onPress={() => setLogScale(v => !v)}>
          <Text style={[SCR.scaleTxt, !logScale && SCR.scaleTxtOn]}>{logScale ? 'LOG' : 'LIN'}</Text>
        </TouchableOpacity>
      </View>

      {/* ── Channel selector ── */}
      <View style={SCR.chRow}>
        {Array.from({ length: maxCh }, (_, i) => (
          <TouchableOpacity key={i}
            style={[SCR.chBtn, channelIdx === i && {
              borderColor: CHANNEL_COLORS[i] + '99',
              backgroundColor: CHANNEL_COLORS[i] + '1a',
            }]}
            onPress={() => setChannelIdx(i)}
          >
            <Text style={[SCR.chTxt, channelIdx === i && { color: CHANNEL_COLORS[i] }]}>
              CH{i + 1}
            </Text>
          </TouchableOpacity>
        ))}
      </View>

      {/* ── Loading progress ── */}
      {loading && (
        <View style={SCR.loadRow}>
          <ActivityIndicator size="small" color="#4a9eff" />
          <Text style={SCR.loadTxt}>加载 {progress.current}/{progress.total} 帧…</Text>
        </View>
      )}

      {/* ── Empty ── */}
      {!loading && frames.length === 0 && (
        <View style={SCR.empty}>
          <Text style={SCR.emptyTxt}>暂无剖面数据</Text>
        </View>
      )}

      {/* ── Charts ── */}
      {frames.length > 0 && (
        <ScrollView style={{ flex: 1 }} showsVerticalScrollIndicator={false}>

          <GateLineChart
            frames={frames} channelIdx={channelIdx}
            gatePositions={gatePositions} gateColors={gateColors}
            logScale={logScale} width={sw} height={CHART_H}
          />

          {/* Gate preview + legend + settings */}
          <View style={SCR.gateSection}>
            <View style={SCR.gateHeader}>
              <Text style={SCR.gateHdr}>
                时间门预览 · {gateConfig.count} 门 · {gateConfig.spacing === 'log' ? '对数' : '线性'}间隔
              </Text>
              <TouchableOpacity style={SCR.settingsBtn} onPress={() => setSettingsVisible(true)}>
                <Text style={SCR.settingsTxt}>⚙ 配置</Text>
              </TouchableOpacity>
            </View>

            <GatePreview
              refFrame={refFrame} channelIdx={channelIdx}
              gatePositions={gatePositions} gateColors={gateColors}
              width={sw}
            />

            <ScrollView horizontal showsHorizontalScrollIndicator={false} style={SCR.legendScroll}>
              <View style={SCR.legendRow}>
                {gatePositions.map((pos, gi) => {
                  const tUs = sampleDepth > 0 && hz > 0 ? pos * (sampleDepth / hz) * 1e6 : 0;
                  return (
                    <View key={gi} style={SCR.legendItem}>
                      <View style={[SCR.legendDot, { backgroundColor: gateColors[gi] }]} />
                      <Text style={SCR.legendTxt}>G{gi + 1} {fmtUs(tUs)}</Text>
                    </View>
                  );
                })}
              </View>
            </ScrollView>
          </View>

        </ScrollView>
      )}

      <SessionPickerModal
        visible={pickerVisible}
        sessions={projectSessions}
        allSessions={allSessions}
        currentId={currentSessionId}
        selectedId={sessionId}
        onSelect={setSessionId}
        onClose={() => setPickerVisible(false)}
      />

      <GateSettingsModal
        visible={settingsVisible}
        config={gateConfig}
        onApply={setGateConfig}
        onClose={() => setSettingsVisible(false)}
        sampleDepth={sampleDepth}
        hz={hz}
      />
    </View>
  );
}

const SCR = StyleSheet.create({
  root:       { flex: 1, backgroundColor: '#0d0d0d' },
  header:     { flexDirection: 'row', alignItems: 'center', paddingHorizontal: 10, paddingVertical: 8, gap: 8 },
  sessBtn:    { flex: 1, flexDirection: 'row', alignItems: 'center', backgroundColor: '#111', borderRadius: 8, paddingHorizontal: 10, paddingVertical: 6, borderWidth: 1, borderColor: '#222' },
  sessBtnTxt: { flex: 1, color: '#6a9eff', fontSize: 11, fontFamily: Platform.OS === 'ios' ? 'Courier' : 'monospace' },
  sessBtnArrow: { color: '#555', fontSize: 10, marginLeft: 4 },
  scaleBtn:   { paddingHorizontal: 8, paddingVertical: 5, borderRadius: 6, borderWidth: 1, borderColor: '#333', backgroundColor: '#1a1a1a' },
  scaleBtnOn: { borderColor: '#4a9eff55', backgroundColor: '#0d2040' },
  scaleTxt:   { color: '#555', fontSize: 11, fontWeight: '700', letterSpacing: 0.5 },
  scaleTxtOn: { color: '#4a9eff' },
  chRow:      { flexDirection: 'row', paddingHorizontal: 10, paddingBottom: 6, gap: 6 },
  chBtn:      { paddingHorizontal: 8, paddingVertical: 4, borderRadius: 6, borderWidth: 1, borderColor: '#2a2a2a' },
  chTxt:      { color: '#555', fontSize: 11, fontWeight: '600' },
  loadRow:    { flexDirection: 'row', alignItems: 'center', gap: 8, padding: 12 },
  loadTxt:    { color: '#556', fontSize: 12 },
  empty:      { flex: 1, justifyContent: 'center', alignItems: 'center', paddingTop: 80 },
  emptyTxt:   { color: '#333', fontSize: 14 },

  gateSection:  { backgroundColor: '#111', marginTop: 2 },
  gateHeader:   { flexDirection: 'row', alignItems: 'center', paddingHorizontal: 12, paddingTop: 8, paddingBottom: 4 },
  gateHdr:      { flex: 1, color: '#444', fontSize: 10, fontWeight: '600', letterSpacing: 0.3 },
  settingsBtn:  { paddingHorizontal: 10, paddingVertical: 4, borderRadius: 6, borderWidth: 1, borderColor: '#2a3a5a', backgroundColor: '#0a1428' },
  settingsTxt:  { color: '#4a7abf', fontSize: 11, fontWeight: '600' },

  legendScroll: { paddingHorizontal: 12, paddingVertical: 8 },
  legendRow:    { flexDirection: 'row', gap: 10 },
  legendItem:   { flexDirection: 'row', alignItems: 'center', gap: 4 },
  legendDot:    { width: 8, height: 8, borderRadius: 4 },
  legendTxt:    { color: '#668', fontSize: 10 },
});
