import { useEffect, useRef, useState } from 'react';
import { Radio, AudioLines, Mic, Activity, SlidersHorizontal, Antenna, Filter, Power } from 'lucide-react';
import {
  GetIcomState, IcomRefresh,
  IcomSetAFGain, IcomSetRFGain, IcomSetNB, IcomSetNBLevel, IcomSetNR, IcomSetNRLevel,
  IcomSetANF, IcomSetAPF, IcomSetAGC, IcomSetPreamp, IcomSetAtt, IcomSetFilter,
  IcomSetRFPower, IcomSetMicGain, IcomSetSplit, IcomTune, IcomSetPTT,
  IcomSetScope, IcomScopeData, IcomSetScopeMode, IcomSetScopeEdges, GetCATState, SetCATFrequency, SetCATMode,
  IcomSetRIT, IcomSetRITOn, IcomSetXITOn,
  IcomSetAntenna, IcomSetPBTInner, IcomSetPBTOuter, IcomSetManualNotch, IcomSetNotchPos,
  IcomSetSquelch, IcomSetComp, IcomSetCompLevel, IcomSetMonitor, IcomSetMonLevel,
  IcomSetVOX, IcomSetVOXGain, IcomSetAntiVOX, IcomSetPower,
} from '../../wailsjs/go/main/App';
import { cn } from '@/lib/utils';
import { useI18n } from '@/lib/i18n';
import { sMeterRST } from '@/lib/rst';
import { MeterBar } from '@/components/MeterBar';
import { ShiftRow } from '@/components/ShiftRow';

type IcomState = {
  available: boolean; model?: string; mode?: string;
  transmitting: boolean; split: boolean;
  s_meter: number; power_meter: number; swr_meter: number;
  rf_power: number; mic_gain: number;
  af_gain: number; rf_gain: number;
  nb: boolean; nb_level: number; nr: boolean; nr_level: number; anf: boolean; apf: boolean;
  agc?: string; preamp: number; att: number; filter: number;
  rit_hz: number; rit_on: boolean; xit_on: boolean;
  antenna: number;
  pbt_inner: number; pbt_outer: number; manual_notch: boolean; notch_pos: number;
  squelch: number; comp: boolean; comp_level: number;
  monitor: boolean; mon_level: number;
  vox: boolean; vox_gain: number; anti_vox: number;
};

const ZERO: IcomState = {
  available: false, transmitting: false, split: false,
  s_meter: 0, power_meter: 0, swr_meter: 0, rf_power: 0, mic_gain: 0,
  af_gain: 0, rf_gain: 0,
  nb: false, nb_level: 0, nr: false, nr_level: 0, anf: false, apf: false,
  preamp: 0, att: 0, filter: 1,
  rit_hz: 0, rit_on: false, xit_on: false,
  antenna: 1,
  pbt_inner: 50, pbt_outer: 50, manual_notch: false, notch_pos: 50,
  squelch: 0, comp: false, comp_level: 0,
  monitor: false, mon_level: 0,
  vox: false, vox_gain: 0, anti_vox: 0,
};

// Band buttons jump the VFO to a sensible default frequency (SSB/CW mix) using
// the plain SetFrequency command — no band-stacking codes needed. Hz values.
type Band = { l: string; hz: number };

const HF_BANDS: Band[] = [
  { l: '160', hz: 1_840_000 }, { l: '80', hz: 3_750_000 }, { l: '40', hz: 7_100_000 },
  { l: '30', hz: 10_130_000 }, { l: '20', hz: 14_150_000 }, { l: '17', hz: 18_130_000 },
  { l: '15', hz: 21_250_000 }, { l: '12', hz: 24_950_000 }, { l: '10', hz: 28_400_000 },
];
const B6 = { l: '6', hz: 50_150_000 };
const B2 = { l: '2', hz: 144_300_000 };       // SSB calling
const B70 = { l: '70cm', hz: 432_200_000 };   // SSB calling
const B23 = { l: '23cm', hz: 1_296_200_000 }; // SSB calling

// Band buttons jump the VFO to a sensible default frequency (SSB/CW mix) using
// the plain SetFrequency command — no band-stacking codes needed.
//
// Which buttons to OFFER depends on the radio, exactly as the attenuator steps
// do below. An IC-9700 has no HF at all, yet the console was showing it 160
// through 6 and none of the bands it actually covers: ten dead buttons, and no
// way to change band from here on the only rig where you would want to.
function bandsFor(model?: string): Band[] {
  const m = (model ?? '').toUpperCase();
  if (m.includes('9700')) return [B2, B70, B23];          // VHF/UHF/SHF only
  if (m.includes('705')) return [...HF_BANDS, B6, B2, B70];
  if (m.includes('9100')) return [...HF_BANDS, B6, B2, B70, B23];
  // 7300 / 7610 / 7100 / unknown: HF + 6 m, the historical list.
  return [...HF_BANDS, B6];
}

// Mode buttons for the console (like RS-BA1's row). SetCATMode picks USB/LSB for
// SSB by frequency and the rig's data variant for digital modes.
const MODES = ['SSB', 'CW', 'RTTY', 'PSK', 'AM', 'FM'];

// Attenuator steps are MODEL-dependent even though the CI-V command (0x11) is the
// same: the value byte is the dB. The IC-7610 (and 7700/7800/7851) have a 6/12/18
// dB stepped attenuator; the IC-7300/705/7100 have a single 20 dB attenuator; the
// IC-9700 a single 10 dB. Offering the wrong steps = a dead button (the rig NAKs
// e.g. 6 dB on a 7300). Default to the common single 20 dB for unknown models.
function attOptions(model?: string): { v: string; l: string }[] {
  const m = (model ?? '').toUpperCase();
  const OFF = { v: '0', l: 'OFF' };
  if (/(7610|7700|7800|7850|7851)/.test(m)) {
    return [OFF, { v: '6', l: '6dB' }, { v: '12', l: '12dB' }, { v: '18', l: '18dB' }];
  }
  if (m.includes('9700')) return [OFF, { v: '10', l: '10dB' }];
  return [OFF, { v: '20', l: '20dB' }]; // IC-7300 / IC-705 / IC-7100 / default
}

// bandOfHz names the amateur band a frequency falls in, so the band row can show
// where the rig actually is. The buttons only ever SENT a frequency and carried
// no active state at all, so nothing was highlighted whatever the rig reported.
// Edges are the ITU/IARU band limits, wide enough to cover regional differences —
// out-of-band (transverter IF, general coverage RX) matches nothing, as it should.
function bandOfHz(hz?: number): string {
  if (!hz || hz <= 0) return '';
  const mhz = hz / 1_000_000;
  const bands: [string, number, number][] = [
    ['160', 1.8, 2.0], ['80', 3.5, 4.0], ['60', 5.25, 5.45], ['40', 7.0, 7.3],
    ['30', 10.1, 10.15], ['20', 14.0, 14.35], ['17', 18.068, 18.168],
    ['15', 21.0, 21.45], ['12', 24.89, 24.99], ['10', 28.0, 29.7],
    // Labels must match the band buttons' exactly — this is only used to light
    // the button for the band the rig is on, and '70' never matched '70cm'.
    ['6', 50.0, 54.0], ['4', 70.0, 70.5], ['2', 144.0, 148.0],
    ['70cm', 430.0, 450.0], ['23cm', 1240.0, 1300.0],
  ];
  for (const [name, lo, hi] of bands) if (mhz >= lo && mhz <= hi) return name;
  return '';
}

// fmtVFO renders a Hz frequency the way an Icom front panel does:
// MHz "." 3-digit-kHz "." 2-digit-(10 Hz). 21032000 → "21.032.00".
function fmtVFO(hz?: number): string {
  if (!hz || hz <= 0) return '––.–––.––';
  const mhz = Math.floor(hz / 1_000_000);
  const khz = Math.floor((hz % 1_000_000) / 1000);
  const h2 = Math.floor((hz % 1000) / 10);
  return `${mhz}.${String(khz).padStart(3, '0')}.${String(h2).padStart(2, '0')}`;
}

// modeMatches marks a mode button active, folding the rig's USB/LSB into SSB.
function modeMatches(btn: string, cur?: string): boolean {
  if (!cur) return false;
  if (btn === 'SSB') return cur === 'SSB' || cur === 'USB' || cur === 'LSB';
  return btn === cur;
}

function Slider({ value, onChange, disabled, accent = '#2563eb', step = 1 }: {
  value: number; onChange: (v: number) => void; disabled?: boolean; accent?: string; step?: number;
}) {
  const v = Math.max(0, Math.min(100, value));
  const ref = useRef<HTMLInputElement>(null);
  // Mouse-wheel adjusts the slider. React's onWheel is passive (preventDefault
  // is ignored), so attach a non-passive native listener; read live values via
  // refs to avoid stale closures.
  const valRef = useRef(value); valRef.current = value;
  const cbRef = useRef(onChange); cbRef.current = onChange;
  const disRef = useRef(disabled); disRef.current = disabled;
  const stepRef = useRef(step); stepRef.current = step;
  useEffect(() => {
    const el = ref.current;
    if (!el) return;
    const onWheel = (e: WheelEvent) => {
      if (disRef.current) return;
      e.preventDefault();
      const d = e.deltaY < 0 ? stepRef.current : -stepRef.current;
      const nv = Math.max(0, Math.min(100, valRef.current + d));
      if (nv !== valRef.current) cbRef.current(nv);
    };
    el.addEventListener('wheel', onWheel, { passive: false });
    return () => el.removeEventListener('wheel', onWheel);
  }, []);
  return (
    <input
      ref={ref}
      type="range" min={0} max={100} value={v} disabled={disabled}
      onChange={(e) => onChange(parseInt(e.target.value, 10))}
      className={cn('flex-1 h-1.5 rounded-full appearance-none cursor-pointer disabled:opacity-30 disabled:cursor-default',
        '[&::-webkit-slider-thumb]:appearance-none [&::-webkit-slider-thumb]:size-3.5 [&::-webkit-slider-thumb]:rounded-full',
        '[&::-webkit-slider-thumb]:bg-card [&::-webkit-slider-thumb]:border-2 [&::-webkit-slider-thumb]:shadow-sm')}
      style={{ background: `linear-gradient(to right, ${accent} ${v}%, #d8cfb8 ${v}%)`, borderColor: accent }}
    />
  );
}

function Segmented({ value, options, onChange }: {
  value: string; options: { v: string; l: string }[]; onChange: (v: string) => void;
}) {
  return (
    <div className="inline-flex rounded-md border border-border overflow-hidden shrink-0">
      {options.map((o) => (
        <button key={o.v} type="button" onClick={() => onChange(o.v)}
          className={cn('px-2 py-1 text-[11px] font-bold tracking-wide transition-colors border-l border-border first:border-l-0',
            value === o.v ? 'bg-primary text-primary-foreground' : 'bg-card text-muted-foreground hover:bg-muted')}>
          {o.l}
        </button>
      ))}
    </div>
  );
}

function Chip({ on, onClick, label }: { on: boolean; onClick: () => void; label: string }) {
  return (
    <button type="button" onClick={onClick}
      className={cn('w-14 shrink-0 px-2 py-1 rounded-md text-[11px] font-bold border transition-colors',
        on ? 'bg-success border-success text-success-foreground' : 'bg-card text-muted-foreground border-border hover:bg-muted')}>
      {label}
    </button>
  );
}

function LevelRow({ label, on, onToggle, value, onLevel }: {
  label: string; on: boolean; onToggle: () => void; value: number; onLevel: (v: number) => void;
}) {
  return (
    <div className="flex items-center gap-2">
      <Chip on={on} onClick={onToggle} label={label} />
      <Slider value={value} disabled={!on} onChange={onLevel} />
      <span className="w-8 text-right text-xs font-mono tabular-nums text-muted-foreground">{value}</span>
    </div>
  );
}

function Card({ icon: Icon, title, accent, children }: { icon: any; title: string; accent?: string; children: React.ReactNode }) {
  return (
    <div className="rounded-xl border border-border bg-card shadow-sm overflow-hidden">
      <div className="flex items-center gap-2 px-3 py-2 border-b border-border/60 bg-muted/30">
        <Icon className="size-4" style={{ color: accent ?? 'var(--primary)' }} />
        <span className="text-xs font-bold uppercase tracking-wider text-foreground/80">{title}</span>
      </div>
      <div className="p-3 space-y-3">{children}</div>
    </div>
  );
}

function Row({ label, children }: { label: string; children: React.ReactNode }) {
  return (
    <div className="flex items-center gap-2">
      <span className="w-16 shrink-0 text-[11px] font-bold uppercase tracking-wider text-muted-foreground">{label}</span>
      {children}
    </div>
  );
}

// Meter — a thin horizontal bar for a live 0-100 reading (S / Po / SWR).
// Optional onClick makes the row a button (used to send the S reading to RST tx).
function Meter({ label, value, accent, scale, onClick, title }: { label: string; value: number; accent: string; scale?: string; onClick?: () => void; title?: string }) {
  const v = Math.max(0, Math.min(100, value));
  const body = (
    <>
      <span className="w-9 shrink-0 text-[11px] font-bold uppercase tracking-wider text-muted-foreground">{label}</span>
      <div className="flex-1 h-2.5 rounded-full bg-muted/60 overflow-hidden">
        <div className="h-full rounded-full transition-[width] duration-150" style={{ width: `${v}%`, background: accent }} />
      </div>
      <span className="w-10 text-right text-[11px] font-mono tabular-nums text-muted-foreground">{scale ?? v}</span>
    </>
  );
  if (onClick) {
    return <button type="button" onClick={onClick} title={title} className="flex items-center gap-2 w-full rounded hover:bg-muted/50 -mx-1 px-1 py-0.5">{body}</button>;
  }
  return <div className="flex items-center gap-2">{body}</div>;
}

// sParts turns the raw 0-100 S-meter into S-unit + dB-over-S9 (S9 ≈ 47% on the
// CI-V 0-255 scale, +60 dB near full scale). Used for both the display label and
// the RST-tx value on click.
function sParts(v: number): { s: number; over: number; label: string } {
  if (v >= 47) {
    const over = Math.max(0, Math.round((v - 47) * 60 / 47));
    return { s: 9, over, label: over > 0 ? `S9+${over}` : 'S9' };
  }
  const s = Math.max(0, Math.min(9, Math.round(v / 5.2)));
  return { s, over: 0, label: `S${s}` };
}

// wfColor maps a 0-1 amplitude to a classic waterfall colour ramp
// (near-black → blue → cyan → green → amber → red).
const WF_STOPS: [number, [number, number, number]][] = [
  [0.0, [8, 12, 28]], [0.22, [26, 58, 138]], [0.42, [0, 150, 190]],
  [0.62, [46, 200, 120]], [0.80, [240, 210, 70]], [1.0, [244, 63, 60]],
];
function wfColor(v: number): [number, number, number] {
  v = Math.max(0, Math.min(1, Math.pow(v, 0.7))); // gamma-lift so the noise floor still has hue
  for (let i = 1; i < WF_STOPS.length; i++) {
    if (v <= WF_STOPS[i][0]) {
      const [a, ca] = WF_STOPS[i - 1], [b, cb] = WF_STOPS[i];
      const f = (v - a) / (b - a || 1);
      return [Math.round(ca[0] + (cb[0] - ca[0]) * f), Math.round(ca[1] + (cb[1] - ca[1]) * f), Math.round(ca[2] + (cb[2] - ca[2]) * f)];
    }
  }
  return WF_STOPS[WF_STOPS.length - 1][1];
}

// ScopePanadapter — enables the rig's spectrum-scope stream and draws the
// reassembled sweep as a modern SDR panadapter: a glowing filled spectrum trace
// on top and a scrolling colour waterfall below. Amplitudes are raw rig scale
// (~0-160), normalised to the tallest recent peak so the trace fills the height.
function ScopePanadapter() {
  const { t } = useI18n();
  const [on, setOn] = useState(false);
  const [fixed, setFixed] = useState(true);
  const canvasRef = useRef<HTMLCanvasElement>(null);
  const wfRef = useRef<HTMLCanvasElement>(null); // waterfall
  const peakRef = useRef(160); // running amplitude ceiling for auto-scale
  const holdRef = useRef<number[]>([]); // per-bin peak-hold line
  // Some radios control their scope over CI-V but never stream it (IC-7851).
  // Saying so beats a black rectangle, which reads as a bug in OpsLog.
  const [unsupported, setUnsupported] = useState(false);
  const spanRef = useRef({ low: 0, high: 0 }); // latest sweep edges, for click-to-tune
  const vfoRef = useRef(0);    // latest VFO frequency, for wheel-tune
  const centerRef = useRef(0); // scope centre we last set (for pan ◀/▶)

  const toggle = () => {
    const next = !on;
    setOn(next);
    IcomSetScope(next).catch(() => {});
  };

  // Centre/pan the FIXED scope: set the edges to centre ±50 kHz (a 100 kHz
  // window). "Centre" uses the live VFO; ◀/▶ shift the window by 50 kHz. This
  // just writes the rig's fixed edges — simple and independent of the waveform
  // decode.
  const SCOPE_HALF = 50_000;
  const applyEdges = (center: number) => {
    if (center <= 0) return;
    centerRef.current = center;
    setFixed(true);
    IcomSetScopeEdges(center - SCOPE_HALF, center + SCOPE_HALF).catch(() => {});
  };
  const centerOnVfo = async () => {
    let c = vfoRef.current;
    if (c <= 0) { try { const cs = await GetCATState(); c = cs?.freq_hz || 0; } catch {} }
    applyEdges(c);
  };
  const pan = (dir: number) => applyEdges((centerRef.current || vfoRef.current) + dir * SCOPE_HALF);
  const setMode = (nextFixed: boolean) => {
    setFixed(nextFixed);
    IcomSetScopeMode(nextFixed).catch(() => {});
  };
  // Stop the stream when the panel unmounts.
  useEffect(() => () => { IcomSetScope(false).catch(() => {}); }, []);

  useEffect(() => {
    if (!on) return;
    let raf = 0, lastSeq = -1, alive = true;
    const tick = async () => {
      if (!alive) return;
      try {
        const sw = await IcomScopeData();
        if (sw?.unsupported) setUnsupported(true);
        if (sw && sw.seq !== lastSeq && sw.amp && sw.amp.length) {
          lastSeq = sw.seq;
          setFixed(sw.fixed);
          spanRef.current = { low: sw.low_hz, high: sw.high_hz };
          let vfo = 0;
          try {
            const cs = await GetCATState();
            vfo = cs?.split && cs.freq_rx_hz ? cs.freq_rx_hz : (cs?.freq_hz || 0);
          } catch {}
          if (vfo > 0) vfoRef.current = vfo;
          draw(sw.amp, sw.low_hz, sw.high_hz, vfoRef.current, sw.fixed);
        }
      } catch {}
      if (alive) raf = window.setTimeout(() => { raf = requestAnimationFrame(tick); }, 40) as unknown as number;
    };
    raf = requestAnimationFrame(tick);
    return () => { alive = false; cancelAnimationFrame(raf); window.clearTimeout(raf); };
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [on]);

  // Double-click tunes the rig to the clicked frequency.
  const onDblClick = (e: React.MouseEvent<HTMLCanvasElement>) => {
    const cv = canvasRef.current;
    const { low, high } = spanRef.current;
    if (!cv || !(low > 0 && high > low)) return;
    const rect = cv.getBoundingClientRect();
    const frac = Math.max(0, Math.min(1, (e.clientX - rect.left) / rect.width));
    const hz = Math.round((low + frac * (high - low)) / 100) * 100; // nearest 100 Hz
    SetCATFrequency(hz).catch(() => {});
  };

  // Mouse-wheel over the scope QSYs ±100 Hz. Non-passive listener so we can
  // preventDefault (else the page scrolls); optimistic vfoRef so quick spins
  // accumulate before the poll reconciles.
  useEffect(() => {
    const el = canvasRef.current;
    if (!el || !on) return;
    const onWheel = (e: WheelEvent) => {
      if (!vfoRef.current) return;
      e.preventDefault();
      const next = vfoRef.current + (e.deltaY < 0 ? 100 : -100);
      vfoRef.current = next;
      SetCATFrequency(next).catch(() => {});
    };
    el.addEventListener('wheel', onWheel, { passive: false });
    return () => el.removeEventListener('wheel', onWheel);
  }, [on]);

  const draw = (amp: number[], lowHz: number, highHz: number, vfoHz: number, fixedMode: boolean) => {
    const cv = canvasRef.current;
    if (!cv) return;
    const dpr = window.devicePixelRatio || 1;
    const w = cv.clientWidth, h = cv.clientHeight;
    if (cv.width !== w * dpr || cv.height !== h * dpr) { cv.width = w * dpr; cv.height = h * dpr; }
    const ctx = cv.getContext('2d');
    if (!ctx) return;
    ctx.setTransform(dpr, 0, 0, dpr, 0, 0);

    // Auto-scale: track the peak, decaying slowly so the floor doesn't jump.
    const peak = Math.max(...amp);
    peakRef.current = Math.max(peak, peakRef.current * 0.95, 40);
    const scale = peakRef.current;
    const n = amp.length;

    // Background — deep navy vertical gradient.
    const bg = ctx.createLinearGradient(0, 0, 0, h);
    bg.addColorStop(0, '#0b1220'); bg.addColorStop(1, '#05070e');
    ctx.fillStyle = bg; ctx.fillRect(0, 0, w, h);

    // Grid.
    ctx.strokeStyle = 'rgba(120,150,200,0.08)';
    ctx.lineWidth = 1;
    for (let i = 1; i < 4; i++) { const y = (h * i) / 4; ctx.beginPath(); ctx.moveTo(0, y); ctx.lineTo(w, y); ctx.stroke(); }
    for (let i = 1; i < 8; i++) { const x = (w * i) / 8; ctx.beginPath(); ctx.moveTo(x, 0); ctx.lineTo(x, h); ctx.stroke(); }

    const xOf = (i: number) => (i / (n - 1)) * w;
    const yOf = (v: number) => h - Math.min(1, v / scale) * h;

    // Peak-hold line (slow decay) — a faint ghost of recent maxima.
    const hold = holdRef.current;
    if (hold.length !== n) hold.length = n, hold.fill(0);
    for (let i = 0; i < n; i++) hold[i] = Math.max(amp[i], hold[i] * 0.92);

    // Filled spectrum area.
    ctx.beginPath();
    ctx.moveTo(0, h);
    for (let i = 0; i < n; i++) ctx.lineTo(xOf(i), yOf(amp[i]));
    ctx.lineTo(w, h); ctx.closePath();
    const grad = ctx.createLinearGradient(0, 0, 0, h);
    grad.addColorStop(0, 'rgba(56,189,248,0.40)');
    grad.addColorStop(1, 'rgba(56,189,248,0.02)');
    ctx.fillStyle = grad; ctx.fill();

    // Peak-hold trace (thin, faint).
    ctx.beginPath();
    for (let i = 0; i < n; i++) { const x = xOf(i), y = yOf(hold[i]); i === 0 ? ctx.moveTo(x, y) : ctx.lineTo(x, y); }
    ctx.strokeStyle = 'rgba(148,197,255,0.35)'; ctx.lineWidth = 1; ctx.stroke();

    // Live spectrum trace with a soft glow.
    ctx.save();
    ctx.shadowColor = 'rgba(56,189,248,0.7)'; ctx.shadowBlur = 6;
    ctx.beginPath();
    for (let i = 0; i < n; i++) { const x = xOf(i), y = yOf(amp[i]); i === 0 ? ctx.moveTo(x, y) : ctx.lineTo(x, y); }
    ctx.strokeStyle = '#7dd3fc'; ctx.lineWidth = 1.5; ctx.lineJoin = 'round'; ctx.stroke();
    ctx.restore();

    // VFO marker: you should ALWAYS see where you are. Exact position when the VFO
    // is inside the span; the centre in CTR mode; clamped to the nearest edge with
    // a sideways arrow in FIX mode when the fixed scope doesn't cover the VFO (so
    // you can tell which way to tune to get it back on-screen).
    const haveVfo = vfoHz > 0 && lowHz > 0 && highHz > lowHz;
    const inSpan = haveVfo && vfoHz >= lowHz && vfoHz <= highHz;
    let markerX = -1;
    let offEdge = 0; // -1 = VFO off the left edge, +1 = off the right
    if (inSpan) markerX = ((vfoHz - lowHz) / (highHz - lowHz)) * w;
    else if (!fixedMode) markerX = w / 2;
    else if (haveVfo) { offEdge = vfoHz < lowHz ? -1 : 1; markerX = offEdge < 0 ? 1 : w - 1; }
    if (markerX >= 0) {
      const x = markerX;
      ctx.fillStyle = 'rgba(244,63,94,0.10)'; ctx.fillRect(x - 5, 0, 10, h);
      ctx.strokeStyle = 'rgba(244,63,94,0.9)'; ctx.lineWidth = 1.25;
      ctx.beginPath(); ctx.moveTo(x, 0); ctx.lineTo(x, h); ctx.stroke();
      ctx.fillStyle = 'rgba(244,63,94,0.95)';
      if (offEdge === 0) {
        ctx.beginPath(); ctx.moveTo(x - 4, 0); ctx.lineTo(x + 4, 0); ctx.lineTo(x, 6); ctx.closePath(); ctx.fill();
      } else {
        const yh = 8; ctx.beginPath(); ctx.moveTo(x, yh - 5); ctx.lineTo(x + offEdge * 7, yh); ctx.lineTo(x, yh + 5); ctx.closePath(); ctx.fill();
      }
    }

    // Frequency scale. In fixed mode the rig reports usable edge frequencies, so
    // we label low/centre/high from them. In centre mode the header frame's edge
    // pair isn't a usable low..high range, but the scope is centred on the VFO —
    // so we always label the centre with the live VFO frequency (which we fetch
    // each sweep), and only add edge labels when the reported edges genuinely
    // bracket the VFO. That guarantees you always see your frequency in CTR.
    const mhz = (hz: number) => (hz / 1e6).toFixed(3);
    ctx.font = '10px ui-monospace, monospace';
    ctx.textBaseline = 'bottom';
    ctx.shadowColor = 'rgba(0,0,0,0.8)'; ctx.shadowBlur = 3;
    ctx.fillStyle = 'rgba(226,232,240,0.85)';
    const label = (txt: string, x: number, align: CanvasTextAlign) => { ctx.textAlign = align; ctx.fillText(txt, x, h - 3); };
    const validEdges = lowHz > 0 && highHz > lowHz;
    if (fixedMode) {
      if (validEdges) {
        label(mhz(lowHz), 4, 'left');
        label(mhz((lowHz + highHz) / 2), w / 2, 'center');
        label(mhz(highHz), w - 4, 'right');
      }
    } else {
      if (validEdges && vfoHz >= lowHz && vfoHz <= highHz) {
        label(mhz(lowHz), 4, 'left');
        label(mhz(highHz), w - 4, 'right');
      }
      if (vfoHz > 0) label(mhz(vfoHz), w / 2, 'center');
    }
    ctx.shadowBlur = 0;

    drawWaterfall(amp, scale);
  };

  // drawWaterfall scrolls the history down one row and paints the newest sweep
  // as a colour-mapped line at the top.
  const drawWaterfall = (amp: number[], scale: number) => {
    const cv = wfRef.current;
    if (!cv) return;
    const w = Math.max(1, cv.clientWidth), h = Math.max(1, cv.clientHeight);
    if (cv.width !== w || cv.height !== h) { cv.width = w; cv.height = h; }
    const ctx = cv.getContext('2d');
    if (!ctx) return;
    // Scroll everything down by one pixel row.
    ctx.drawImage(cv, 0, 0, w, h - 1, 0, 1, w, h - 1);
    // Paint the new top row.
    const row = ctx.createImageData(w, 1);
    const n = amp.length;
    for (let x = 0; x < w; x++) {
      const i = Math.min(n - 1, Math.round((x / (w - 1)) * (n - 1)));
      const [r, g, b] = wfColor(amp[i] / scale);
      const o = x * 4;
      row.data[o] = r; row.data[o + 1] = g; row.data[o + 2] = b; row.data[o + 3] = 255;
    }
    ctx.putImageData(row, 0, 0);
  };

  // Collapsible card: when the scope is off, only the header band shows (the
  // canvas is hidden entirely) so it doesn't waste vertical space. The CTR/FIX
  // and ON/OFF controls live in the header itself.
  return (
    <div className="rounded-xl border border-border bg-card shadow-sm overflow-hidden">
      <div className="flex items-center gap-2 px-3 py-2 border-b border-border/60 bg-muted/30">
        <Activity className="size-4" style={{ color: '#38bdf8' }} />
        <span className="text-xs font-bold uppercase tracking-wider text-foreground/80">{t('icmp.spectrum')}</span>
        <div className="ml-auto flex items-center gap-2 shrink-0">
          {on && (
            <div className="inline-flex rounded-md border border-border overflow-hidden">
              <button type="button" onClick={() => pan(-1)} title={t('icmp.scopePanDown')}
                className="px-2 py-1 text-xs font-bold bg-card text-muted-foreground hover:bg-muted border-r border-border">◀</button>
              <button type="button" onClick={centerOnVfo} title={t('icmp.scopeCenterVfo')}
                className="px-2 py-1 text-[11px] font-bold bg-card text-muted-foreground hover:bg-muted border-r border-border">⊙</button>
              <button type="button" onClick={() => pan(1)} title={t('icmp.scopePanUp')}
                className="px-2 py-1 text-xs font-bold bg-card text-muted-foreground hover:bg-muted">▶</button>
            </div>
          )}
          {on && (
            <Segmented value={fixed ? 'FIX' : 'CTR'} options={[{ v: 'CTR', l: 'CTR' }, { v: 'FIX', l: 'FIX' }]}
              onChange={(v) => setMode(v === 'FIX')} />
          )}
          <Chip label={on ? 'ON' : 'OFF'} on={on} onClick={toggle} />
        </div>
      </div>
      {on && unsupported && (
        <div className="px-3 py-2 text-xs text-muted-foreground">{t('icmp.scopeNoStream')}</div>
      )}
      {on && !unsupported && (
        <div className="p-3">
          <div className="rounded-xl overflow-hidden ring-1 ring-info/20 shadow-lg shadow-sky-500/5 bg-[#05070e]">
            <canvas ref={canvasRef} onDoubleClick={onDblClick}
              className="w-full block cursor-crosshair" style={{ height: 140 }} />
            <canvas ref={wfRef} className="w-full block" style={{ height: 96 }} />
          </div>
        </div>
      )}
    </div>
  );
}

// IcomPanel — full control surface (RX DSP + TX) for an Icom on the CI-V backend.
// Unlike the Flex (which pushes state), the Icom is polled: meters/TX state are
// read every cache cycle; DSP set-controls are optimistic and reconcile on the
// next poll. Front-panel knob changes for DSP show after ↻ Refresh.
export function IcomPanel({ onReportRST, isNetwork = false }: { onReportRST?: (rst: string) => void; isNetwork?: boolean } = {}) {
  const { t } = useI18n();
  const [st, setSt] = useState<IcomState>(ZERO);
  const [cat, setCat] = useState<any>(null); // RigState (freq/mode/split) for the VFO display
  const [tuning, setTuning] = useState(false);
  const txRef = useRef(false);
  const stRef = useRef<IcomState>(ZERO); stRef.current = st;

  const load = () => {
    GetIcomState().then((s) => setSt((s ?? ZERO) as IcomState)).catch(() => {});
    GetCATState().then((c) => setCat(c ?? null)).catch(() => {});
  };
  const setMode = (m: string) => { setCat((c: any) => (c ? { ...c, mode: m } : c)); SetCATMode(m).catch(() => {}); };
  // Initial one-shot read of the rig's DSP snapshot on mount (the 500ms poll only
  // re-reads the cache; the backend also loads DSP on the first responsive read).
  const refresh = async () => {
    try { await IcomRefresh(); } catch {}
    await load();
  };

  useEffect(() => {
    refresh();
    const id = window.setInterval(load, 500); // fast poll so meters/TX feel live
    return () => window.clearInterval(id);
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, []);

  // Optimistic local update + fire the command; the cache poll reconciles.
  const set = (patch: Partial<IcomState>, fn: () => Promise<void>) => {
    setSt((s) => ({ ...s, ...patch }));
    fn().catch(() => {});
  };

  const toggleMox = () => {
    const next = !txRef.current;
    txRef.current = next;
    set({ transmitting: next }, () => IcomSetPTT(next));
  };

  const tune = async () => {
    setTuning(true);
    try { await IcomTune(); } catch {}
    window.setTimeout(() => setTuning(false), 4000);
  };

  // RIT/ΔTX offset (signed Hz, clamped ±9999). Optimistic like the DSP controls.
  const setRit = (hz: number) => {
    const v = Math.max(-9999, Math.min(9999, hz));
    set({ rit_hz: v }, () => IcomSetRIT(v));
  };

  // Ctrl+Left/Right shifts the RIT by ±10 Hz while RIT is active — a keyboard
  // clarifier for zero-beating a caller without touching the mouse.
  useEffect(() => {
    const onKey = (e: KeyboardEvent) => {
      if (!e.ctrlKey || (e.key !== 'ArrowLeft' && e.key !== 'ArrowRight')) return;
      const s = stRef.current;
      if (!s.available || !s.rit_on) return;
      e.preventDefault();
      setRit(s.rit_hz + (e.key === 'ArrowRight' ? 10 : -10));
    };
    window.addEventListener('keydown', onKey);
    return () => window.removeEventListener('keydown', onKey);
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, []);

  if (!st.available) {
    return (
      <div className="h-full flex items-center justify-center text-sm text-muted-foreground p-6 text-center">
        {t('icmp.notConnected')}
      </div>
    );
  }

  const tx = st.transmitting;
  // VFO readout. In split the active/listening VFO is RX (freq_rx_hz) and the
  // other is TX (freq_hz); otherwise there's a single VFO (freq_hz).
  const split = !!cat?.split;
  const mainHz: number = split ? (cat?.freq_rx_hz || 0) : (cat?.freq_hz || 0);
  const subHz: number = split ? (cat?.freq_hz || 0) : 0;
  const curMode: string = cat?.mode || st.mode || '';
  // Mode-dependent controls: VOX / speech-comp / mic are voice-only (hidden on
  // CW and data); APF (audio peak filter) is CW-only. Fold USB/LSB into phone.
  const um = curMode.toUpperCase();
  const isCW = um === 'CW' || um === 'CWR';
  const isPhone = um === 'SSB' || um === 'USB' || um === 'LSB' || um === 'AM' || um === 'FM';

  return (
    <div className="h-full min-h-0 overflow-auto bg-background">
      <div className="max-w-5xl mx-auto p-3 space-y-3">
      {/* Header strip: model + mode + live RX/TX indicator + split badge. */}
      <div className="flex items-center justify-between rounded-xl border border-border bg-card px-3 py-2 shadow-sm">
        <div className="flex items-center gap-2">
          <span className={cn('inline-flex items-center gap-1.5 rounded-md px-2 py-1 text-[11px] font-bold uppercase tracking-wider',
            tx ? 'bg-destructive text-destructive-foreground' : 'bg-success text-success-foreground')}>
            <span className={cn('size-2 rounded-full', tx ? 'bg-card animate-pulse' : 'bg-card/90')} />
            {tx ? 'TX' : 'RX'}
          </span>
          <span className="text-sm font-bold">{st.model || 'Icom'}</span>
          {st.mode ? <span className="text-xs font-mono text-muted-foreground">{st.mode}</span> : null}
          {st.split ? <span className="rounded-md bg-warning/20 px-1.5 py-0.5 text-[10px] font-bold uppercase tracking-wider text-warning">Split</span> : null}
        </div>
        <div className="flex items-center gap-1.5">
          {/* Radio power ON / OFF — NETWORK only. Over USB the CI-V interface is
              unpowered while the rig is off, so power-ON can't reach it (OFF works
              but ON doesn't); hiding both avoids a dead button. On the network the
              rig's LAN server stays alive in standby, so both work. */}
          {isNetwork && (
            <>
              <button type="button" onClick={() => IcomSetPower(true).catch(() => {})} title={t('icmp.powerOnHint')}
                className="inline-flex items-center gap-1 rounded-md border border-success/60 bg-success/10 px-2 py-1 text-xs font-bold text-success hover:bg-success/20">
                <Power className="size-3.5" /> ON
              </button>
              <button type="button" onClick={() => IcomSetPower(false).catch(() => {})} title={t('icmp.powerOffHint')}
                className="inline-flex items-center gap-1 rounded-md border border-destructive/60 bg-destructive/10 px-2 py-1 text-xs font-bold text-destructive hover:bg-destructive/20">
                <Power className="size-3.5" /> OFF
              </button>
            </>
          )}
        </div>
      </div>

      {/* VFO readout — the RS-BA1-style twin display: MAIN (active) + SUB, the big
          tabular frequency, mode badge, band, and the RIT/ΔTX offset. */}
      <div className="rounded-xl border border-border bg-muted/25 shadow-inner overflow-hidden">
        <div className="grid grid-cols-2 divide-x divide-border/60">
          {/* MAIN VFO */}
          <div className="px-4 py-3">
            <div className="flex items-center justify-between mb-1.5">
              <span className={cn('text-[10px] font-bold uppercase tracking-widest', tx ? 'text-destructive' : 'text-success')}>{tx ? 'Main · TX' : 'Main'}</span>
              {curMode ? <span className="rounded px-1.5 py-0.5 text-[10px] font-bold bg-primary/15 text-primary">{curMode}</span> : null}
            </div>
            <div className="font-mono font-bold tabular-nums leading-none text-foreground" style={{ fontSize: 'clamp(1.5rem, 4.5vw, 2.25rem)' }}>{fmtVFO(mainHz)}</div>
            <div className="mt-1.5 flex items-center gap-2 text-[11px] font-mono text-muted-foreground">
              <span>{cat?.band || (mainHz ? '' : '—')}</span>
              {st.rit_on ? <span className="text-primary">RIT {st.rit_hz > 0 ? '+' : st.rit_hz < 0 ? '−' : ''}{Math.abs(st.rit_hz)}</span> : null}
              {st.xit_on ? <span className="text-warning">ΔTX</span> : null}
            </div>
          </div>
          {/* SUB VFO (populated in split; dimmed otherwise) */}
          <div className={cn('px-4 py-3', !split && 'opacity-40')}>
            <div className="flex items-center justify-between mb-1.5">
              <span className="text-[10px] font-bold uppercase tracking-widest text-muted-foreground">Sub</span>
              {split ? <span className="rounded px-1.5 py-0.5 text-[10px] font-bold bg-warning/15 text-warning">SPLIT</span> : null}
            </div>
            <div className="font-mono font-bold tabular-nums leading-none text-muted-foreground" style={{ fontSize: 'clamp(1.5rem, 4.5vw, 2.25rem)' }}>{fmtVFO(subHz)}</div>
            <div className="mt-1.5 text-[11px] font-mono text-muted-foreground">{split ? 'TX' : ''}</div>
          </div>
        </div>
        {/* Mode selector row (RS-BA1's SSB/CW/RTTY/PSK/AM/FM). */}
        <div className="grid grid-cols-6 border-t border-border/60 divide-x divide-border/60">
          {MODES.map((m) => {
            const on = modeMatches(m, curMode);
            return (
              <button key={m} type="button" onClick={() => setMode(m)}
                className={cn('py-1.5 text-[11px] font-bold tracking-wide transition-colors',
                  on ? 'bg-primary text-primary-foreground' : 'bg-card/40 text-muted-foreground hover:bg-muted')}>
                {m}
              </button>
            );
          })}
        </div>
      </div>

      {/* Live meters — always visible: S (RX, click → RST), Po in watts, SWR. */}
      <div className="rounded-xl border border-border bg-card px-3 py-2.5 shadow-sm grid grid-cols-1 sm:grid-cols-3 gap-x-5 gap-y-2">
        {(() => { const sp = sParts(st.s_meter); return (
          <Meter label="S" value={st.s_meter} accent="#22c55e" scale={sp.label}
            title={onReportRST ? t('rst.clickToFill') : undefined}
            onClick={onReportRST ? () => onReportRST(sMeterRST(sp.s, sp.over, st.mode)) : undefined} />
        ); })()}
        <Meter label="Po" value={st.power_meter} accent="#ef4444" scale={`${st.power_meter} W`} />
        <Meter label="SWR" value={st.swr_meter} accent="#f59e0b" scale={st.swr_meter > 0 ? `${(1 + st.swr_meter / 33.3).toFixed(1)}` : '1.0'} />
      </div>

      {/* Spectrum panadapter (full width). */}
      <ScopePanadapter />

      <div className="grid grid-cols-1 lg:grid-cols-2 gap-3">
      {/* Band buttons + antenna selection. */}
      <Card icon={Antenna} title={t('icmp.bandsAntenna')} accent="#0891b2">
        <div className="grid grid-cols-5 gap-1.5">
          {bandsFor(st.model).map((b) => {
            const here = bandOfHz(mainHz) === b.l;
            return (
              <button key={b.l} type="button" onClick={() => SetCATFrequency(b.hz).catch(() => {})}
                title={here ? t('icmp.bandCurrent', { b: b.l }) : undefined}
                className={cn('px-1 py-1.5 rounded-md text-[11px] font-bold border transition-colors',
                  here
                    ? 'border-primary bg-primary text-primary-foreground shadow-[0_0_8px] shadow-primary/40'
                    : 'border-border bg-card text-foreground hover:bg-muted')}>
                {b.l}
              </button>
            );
          })}
        </div>
        <Row label={t('icmp.antenna')}>
          <Segmented value={String(st.antenna)} options={[{ v: '1', l: 'ANT1' }, { v: '2', l: 'ANT2' }]}
            onChange={(v) => set({ antenna: parseInt(v) }, () => IcomSetAntenna(parseInt(v)))} />
        </Row>
      </Card>

      {/* Clarifiers: RIT & ΔTX (XIT) — wheel or ± to shift, Ctrl+←/→ shifts RIT. */}
      <Card icon={SlidersHorizontal} title={t('icmp.clarifiers')} accent="#8b5cf6">
        <ShiftRow label="RIT" accent="#8b5cf6" on={st.rit_on} hz={st.rit_hz}
          onToggle={() => set({ rit_on: !st.rit_on }, () => IcomSetRITOn(!st.rit_on))}
          onSet={setRit} />
        <ShiftRow label="ΔTX" accent="#f59e0b" on={st.xit_on} hz={st.rit_hz}
          onToggle={() => set({ xit_on: !st.xit_on }, () => IcomSetXITOn(!st.xit_on))}
          onSet={setRit} />
        <p className="text-[11px] text-muted-foreground">{t('icmp.ritHint')}</p>
      </Card>

      {/* Transmit controls. */}
      <Card icon={Mic} title={t('icmp.transmit')} accent="#ef4444">
        <Row label={t('icmp.power')}>
          <Slider value={st.rf_power} accent="#ef4444" onChange={(v) => set({ rf_power: v }, () => IcomSetRFPower(v))} />
          <span className="w-8 text-right text-xs font-mono tabular-nums text-muted-foreground">{st.rf_power}</span>
        </Row>
        {isPhone && (
          <Row label={t('icmp.mic')}>
            <Slider value={st.mic_gain} accent="#ef4444" onChange={(v) => set({ mic_gain: v }, () => IcomSetMicGain(v))} />
            <span className="w-8 text-right text-xs font-mono tabular-nums text-muted-foreground">{st.mic_gain}</span>
          </Row>
        )}
        <div className="flex items-center gap-2 pt-1">
          <button type="button" onClick={toggleMox}
            className={cn('flex-1 px-3 py-1.5 rounded-md text-xs font-bold border transition-colors',
              tx ? 'bg-destructive border-destructive text-destructive-foreground' : 'bg-card text-foreground border-border hover:bg-muted')}>
            {tx ? 'TX ON' : 'MOX'}
          </button>
          <Chip label="SPLIT" on={st.split} onClick={() => set({ split: !st.split }, () => IcomSetSplit(!st.split))} />
          <button type="button" onClick={tune} disabled={tuning}
            className={cn('w-14 shrink-0 px-2 py-1.5 rounded-md text-[11px] font-bold border transition-colors',
              tuning ? 'bg-warning border-warning text-warning-foreground animate-pulse' : 'bg-card text-foreground border-border hover:bg-muted')}>
            TUNE
          </button>
        </div>
        {/* Monitor (all modes) + speech processor / VOX (voice modes only —
            they don't exist on CW or data). */}
        <div className="pt-2 mt-1 border-t border-border/60 space-y-3">
          <LevelRow label="MON" on={st.monitor} value={st.mon_level}
            onToggle={() => set({ monitor: !st.monitor }, () => IcomSetMonitor(!st.monitor))}
            onLevel={(v) => set({ mon_level: v }, () => IcomSetMonLevel(v))} />
          {isPhone && (
            <>
              <LevelRow label="COMP" on={st.comp} value={st.comp_level}
                onToggle={() => set({ comp: !st.comp }, () => IcomSetComp(!st.comp))}
                onLevel={(v) => set({ comp_level: v }, () => IcomSetCompLevel(v))} />
              <LevelRow label="VOX" on={st.vox} value={st.vox_gain}
                onToggle={() => set({ vox: !st.vox }, () => IcomSetVOX(!st.vox))}
                onLevel={(v) => set({ vox_gain: v }, () => IcomSetVOXGain(v))} />
              <Row label="Anti-VOX">
                <Slider value={st.anti_vox} disabled={!st.vox} onChange={(v) => set({ anti_vox: v }, () => IcomSetAntiVOX(v))} />
                <span className="w-8 text-right text-xs font-mono tabular-nums text-muted-foreground">{st.anti_vox}</span>
              </Row>
            </>
          )}
        </div>
      </Card>

      <Card icon={Radio} title={t('icmp.receive')} accent="#2563eb">
        <Row label="AF">
          <Slider value={st.af_gain} onChange={(v) => set({ af_gain: v }, () => IcomSetAFGain(v))} />
          <span className="w-8 text-right text-xs font-mono tabular-nums text-muted-foreground">{st.af_gain}</span>
        </Row>
        <Row label="RF">
          <Slider value={st.rf_gain} onChange={(v) => set({ rf_gain: v }, () => IcomSetRFGain(v))} />
          <span className="w-8 text-right text-xs font-mono tabular-nums text-muted-foreground">{st.rf_gain}</span>
        </Row>
        <Row label={t('icmp.squelch')}>
          <Slider value={st.squelch} onChange={(v) => set({ squelch: v }, () => IcomSetSquelch(v))} />
          <span className="w-8 text-right text-xs font-mono tabular-nums text-muted-foreground">{st.squelch}</span>
        </Row>
        <Row label="AGC">
          <Segmented value={st.agc || ''} options={[{ v: 'FAST', l: 'FAST' }, { v: 'MID', l: 'MID' }, { v: 'SLOW', l: 'SLOW' }]}
            onChange={(v) => set({ agc: v }, () => IcomSetAGC(v))} />
        </Row>
        <Row label={t('icmp.preamp')}>
          <Segmented value={String(st.preamp)} options={[{ v: '0', l: 'OFF' }, { v: '1', l: 'P1' }, { v: '2', l: 'P2' }]}
            onChange={(v) => set({ preamp: parseInt(v) }, () => IcomSetPreamp(parseInt(v)))} />
        </Row>
        <Row label="Att">
          <Segmented value={String(st.att)} options={attOptions(cat?.rig)}
            onChange={(v) => set({ att: parseInt(v) }, () => IcomSetAtt(parseInt(v)))} />
        </Row>
        <Row label={t('icmp.filter')}>
          <Segmented value={String(st.filter)} options={[{ v: '1', l: 'FIL1' }, { v: '2', l: 'FIL2' }, { v: '3', l: 'FIL3' }]}
            onChange={(v) => set({ filter: parseInt(v) }, () => IcomSetFilter(parseInt(v)))} />
        </Row>
      </Card>

      {/* Twin PBT + manual notch. Sliders are 0-100 with 50 = centre. */}
      <Card icon={Filter} title={t('icmp.passband')} accent="#7c3aed">
        <Row label="PBT-IN">
          <Slider value={st.pbt_inner} accent="#7c3aed" onChange={(v) => set({ pbt_inner: v }, () => IcomSetPBTInner(v))} />
          <span className="w-8 text-right text-xs font-mono tabular-nums text-muted-foreground">{st.pbt_inner - 50 > 0 ? '+' : ''}{st.pbt_inner - 50}</span>
        </Row>
        <Row label="PBT-OUT">
          <Slider value={st.pbt_outer} accent="#7c3aed" onChange={(v) => set({ pbt_outer: v }, () => IcomSetPBTOuter(v))} />
          <span className="w-8 text-right text-xs font-mono tabular-nums text-muted-foreground">{st.pbt_outer - 50 > 0 ? '+' : ''}{st.pbt_outer - 50}</span>
        </Row>
        <button type="button"
          onClick={() => { set({ pbt_inner: 50 }, () => IcomSetPBTInner(50)); set({ pbt_outer: 50 }, () => IcomSetPBTOuter(50)); }}
          className="w-full py-1 rounded-md text-[11px] font-bold border border-border bg-card text-muted-foreground hover:bg-muted">
          {t('icmp.pbtCenter')}
        </button>
        <div className="pt-1 border-t border-border/60 space-y-3">
          <div className="flex items-center gap-2">
            <Chip label="MN" on={st.manual_notch} onClick={() => set({ manual_notch: !st.manual_notch }, () => IcomSetManualNotch(!st.manual_notch))} />
            <Slider value={st.notch_pos} disabled={!st.manual_notch} accent="#7c3aed" onChange={(v) => set({ notch_pos: v }, () => IcomSetNotchPos(v))} />
            <span className="w-8 text-right text-xs font-mono tabular-nums text-muted-foreground">{st.notch_pos}</span>
          </div>
          <p className="text-[11px] text-muted-foreground">{t('icmp.manualNotch')}</p>
        </div>
      </Card>

      <Card icon={AudioLines} title={t('icmp.noiseNotch')} accent="#16a34a">
        <LevelRow label="NB" on={st.nb} value={st.nb_level}
          onToggle={() => set({ nb: !st.nb }, () => IcomSetNB(!st.nb))}
          onLevel={(v) => set({ nb_level: v }, () => IcomSetNBLevel(v))} />
        <LevelRow label="NR" on={st.nr} value={st.nr_level}
          onToggle={() => set({ nr: !st.nr }, () => IcomSetNR(!st.nr))}
          onLevel={(v) => set({ nr_level: v }, () => IcomSetNRLevel(v))} />
        <div className="flex items-center gap-2">
          <Chip label="ANF" on={st.anf} onClick={() => set({ anf: !st.anf }, () => IcomSetANF(!st.anf))} />
          <span className="text-xs text-muted-foreground">{t('icmp.autoNotch')}</span>
        </div>
        {/* APF (audio peak filter) — CW only: peaks the CW tone. */}
        {isCW && (
          <div className="flex items-center gap-2">
            <Chip label="APF" on={st.apf} onClick={() => set({ apf: !st.apf }, () => IcomSetAPF(!st.apf))} />
            <span className="text-xs text-muted-foreground">{t('icmp.apf')}</span>
          </div>
        )}
      </Card>
      </div>
      </div>
    </div>
  );
}
