import { useEffect, useRef, useState } from 'react';
import { Radio, Zap, Power, AudioLines, Flame, Gauge, Volume2, VolumeX, ChevronDown, SlidersHorizontal } from 'lucide-react';
import {
  GetFlexState, FlexATUStart, FlexATUBypass, FlexSetATUMemories, FlexSetPower, FlexSetTunePower, FlexTune, FlexSetVox, FlexSetVoxLevel, FlexSetVoxDelay,
  FlexSetProcessor, FlexSetProcessorLevel, FlexSetMon, FlexSetMonLevel, FlexSetMic,
  FlexMox, FlexAmpOperate,
  GetPGXLStatus, PGXLSetFanMode,
  GetTunerGeniusStatus, GetTunerGeniusSettings,
  GetAmpStatuses, AmpOperate, AmpPower, AmpPowerLevel,
  FlexSetAGCMode, FlexSetAGCThreshold, FlexSetAudioLevel, FlexSetMute, FlexSetRXAntenna, FlexSetTXAntenna, FlexSetSplit, FlexSetActiveSlice, FlexSetTXSlice,
  GetFlexRSTChase, SaveFlexRSTChase, SetFlexRSTChaseEnabled,
  FlexSetRIT, FlexSetRITFreq, FlexSetXIT, FlexSetXITFreq,
  FlexSetNB, FlexSetNBLevel, FlexSetNR, FlexSetNRLevel, FlexSetANF, FlexSetANFLevel,
  FlexSetLMSNR, FlexSetLMSNRLevel, FlexSetLMSANF, FlexSetLMSANFLevel,
  FlexSetSpeexNR, FlexSetSpeexNRLevel, FlexSetRNN, FlexSetANFT, FlexSetNRF, FlexSetNRFLevel, FlexSetTXDAX,
  FlexSetWNB, FlexSetWNBLevel, FlexSetTXFilter, FlexSetMicProfile,
  FlexSetAPF, FlexSetAPFLevel, FlexSetCWSpeed, FlexSetCWPitch, FlexSetCWBreakInDelay,
  FlexSetCWSidetone, FlexSetSidetoneLevel, FlexSetCWFilter, FlexSetFilter,
} from '../../wailsjs/go/main/App';
import { EventsOn } from '../../wailsjs/runtime/runtime';
import { cn } from '@/lib/utils';
import { useI18n } from '@/lib/i18n';
import { sMeterRST } from '@/lib/rst';
import { TunerCard } from '@/components/TunerCard';
import { MeterBar } from '@/components/MeterBar';
import type { TGStatus } from '@/components/TunerGeniusPanel';

type FlexState = {
  available: boolean; model?: string;
  rf_power: number; tune_power: number; tune: boolean; transmitting: boolean;
  vox_enable: boolean; vox_level: number; vox_delay: number;
  proc_enable: boolean; proc_level: number;
  mon: boolean; mon_level: number; mic_level: number;
  atu_status?: string; atu_memories: boolean;
  rx_avail: boolean; agc_mode?: string; agc_threshold: number; audio_level: number; mute: boolean;
  rx_ant?: string; tx_ant?: string; ant_list?: string[]; tx_ant_list?: string[];
  split: boolean; rx_freq_hz?: number; tx_freq_hz?: number;
  rit: boolean; rit_freq: number; xit: boolean; xit_freq: number;
  nb: boolean; nb_level: number; nr: boolean; nr_level: number; anf: boolean; anf_level: number;
  wnb: boolean; wnb_level: number;
  // SmartSDR v4 DSP (8000/Aurora) — dsp_v4 is true once the radio reports them.
  lms_nr?: boolean; lms_nr_level?: number; lms_anf?: boolean; lms_anf_level?: number;
  speex_nr?: boolean; speex_nr_level?: number; rnn?: boolean; anft?: boolean;
  nrf?: boolean; nrf_level?: number; dsp_v4?: boolean;
  dax_ch?: number; tx_dax?: boolean;
  tx_filter_low: number; tx_filter_high: number; mic_profile?: string; mic_profiles?: string[];
  mode?: string;
  cw_speed: number; cw_pitch: number; cw_break_in_delay: number; cw_sidetone: boolean; cw_mon_level: number;
  apf: boolean; apf_level: number; filter_lo: number; filter_hi: number;
  amp_available: boolean; amp_model?: string; amp_operate: boolean; amp_fault?: string;
  meters?: Meter[];
  slices?: FlexSlice[];
};

type FlexSlice = { index: number; letter: string; freq_hz: number; mode?: string; band?: string; active: boolean; tx: boolean };
type Meter = { id: number; src?: string; name?: string; unit?: string; slice?: number; value: number; lo: number; hi: number };

const ZERO: FlexState = {
  available: false, rf_power: 0, tune_power: 0, tune: false, transmitting: false,
  vox_enable: false, vox_level: 0, vox_delay: 0, proc_enable: false, proc_level: 0,
  mon: false, mon_level: 0, mic_level: 0, atu_memories: false,
  rx_avail: false, agc_threshold: 0, audio_level: 0, mute: false, split: false,
  rit: false, rit_freq: 0, xit: false, xit_freq: 0,
  nb: false, nb_level: 0, nr: false, nr_level: 0, anf: false, anf_level: 0,
  wnb: false, wnb_level: 0, tx_filter_low: 0, tx_filter_high: 0,
  cw_speed: 25, cw_pitch: 600, cw_break_in_delay: 30, cw_sidetone: true, cw_mon_level: 0,
  apf: false, apf_level: 0, filter_lo: 0, filter_hi: 0,
  amp_available: false, amp_operate: false,
};

function Slider({ value, onChange, disabled, accent = '#16a34a', step = 1, max = 100 }: {
  value: number; onChange: (v: number) => void; disabled?: boolean; accent?: string; step?: number; max?: number;
}) {
  const v = Math.max(0, Math.min(max, value));
  const pct = max > 0 ? (v / max) * 100 : 0;
  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;
  const maxRef = useRef(max); maxRef.current = max;
  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(maxRef.current, 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={max} 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 [&::-webkit-slider-thumb]:cursor-pointer')}
      style={{ background: `linear-gradient(to right, ${accent} ${pct}%, #d8cfb8 ${pct}%)`, borderColor: accent }}
    />
  );
}

// Segmented — radio-style multi-choice (AGC, Processor preset).
function Segmented({ value, options, onChange, disabled }: {
  value: string; options: { v: string; l: string }[]; onChange: (v: string) => void; disabled?: boolean;
}) {
  return (
    <div className="inline-flex rounded-md border border-border overflow-hidden shrink-0">
      {options.map((o) => (
        <button key={o.v} type="button" disabled={disabled} onClick={() => onChange(o.v)}
          className={cn('px-2 py-1 text-[11px] font-bold tracking-wide transition-colors disabled:opacity-30 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>
  );
}

// Chip — a compact on/off pill (NB/NR/ANF/VOX/MON…).
function Chip({ on, onClick, label, disabled, accent = 'emerald', title }: {
  on: boolean; onClick: () => void; label: string; disabled?: boolean; accent?: 'emerald' | 'violet' | 'cyan' | 'amber'; title?: string;
}) {
  const onCls = {
    emerald: 'bg-success border-success text-success-foreground',
    violet: 'bg-info border-info text-info-foreground',
    cyan: 'bg-info border-info text-info-foreground',
    amber: 'bg-warning border-warning text-warning-foreground',
  }[accent];
  return (
    <button type="button" onClick={onClick} disabled={disabled} title={title}
      className={cn(
        // min width (not fixed) so a longer label like STONE keeps symmetric
        // padding instead of overflowing; short labels (NB/NR/APF) stay aligned.
        'min-w-[3.5rem] shrink-0 px-2.5 py-1 rounded-md text-[11px] font-bold border text-center tracking-wide transition-all disabled:opacity-30',
        on ? cn(onCls, 'shadow-sm') : 'bg-card text-muted-foreground border-border hover:bg-muted hover:border-muted-foreground/30')}>
      {label}
    </button>
  );
}

// SmartSDR reports the tuner as an enum. It is worth decoding rather than
// showing raw: TUNE_FAIL and "never tuned" both leave the radio looking normal,
// and the operator transmits into a bad match either way.
function atuLabel(status: string | undefined, t: (k: string) => string): string {
  switch ((status || '').toUpperCase()) {
    case 'TUNE_IN_PROGRESS':   return t('flxp.atuTuning');
    case 'TUNE_SUCCESSFUL':
    case 'TUNE_OK':            return t('flxp.atuOk');
    case 'TUNE_FAIL':
    case 'TUNE_FAIL_BYPASS':   return t('flxp.atuFail');
    case 'TUNE_BYPASS':
    case 'TUNE_MANUAL_BYPASS': return t('flxp.atuBypassed');
    case 'TUNE_ABORTED':       return t('flxp.atuAborted');
    case 'TUNE_NOT_STARTED':
    case 'NONE':
    case '':                   return t('flxp.atuIdle');
    default:                   return status || '';
  }
}

function atuTone(status: string | undefined): string {
  switch ((status || '').toUpperCase()) {
    case 'TUNE_SUCCESSFUL':
    case 'TUNE_OK':            return 'text-success';
    case 'TUNE_FAIL':
    case 'TUNE_FAIL_BYPASS':   return 'text-danger';
    case 'TUNE_IN_PROGRESS':   return 'text-warning';
    default:                   return 'text-muted-foreground';
  }
}

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

// OffsetRow — RIT / XIT: a switch plus one signed offset field you scrub with the
// wheel (or ± , or the arrow keys once focused; hold Ctrl/Shift for 100 Hz steps).
// Deliberately the same control as the Icom panel's ShiftRow, so the two rigs are
// driven identically.
//
// The offset is NOT cleared when the switch goes off: SmartSDR keeps it, so
// flipping RIT back on returns you exactly where you were, like the radio's own
// knob. Zeroing it is a separate, deliberate act — that is what the 0 button is for.
const OFFSET_MAX = 99999; // SmartSDR's limit
function OffsetRow({ label, on, onToggle, hz, onHz, disabled, title }: {
  label: string; on: boolean; onToggle: () => void; hz: number; onHz: (v: number) => void;
  disabled?: boolean; title?: string;
}) {
  const ref = useRef<HTMLDivElement>(null);
  const step = useRef((_d: number) => {});
  step.current = (d: number) => onHz(Math.max(-OFFSET_MAX, Math.min(OFFSET_MAX, (hz || 0) + d)));

  // React's onWheel is passive, so preventDefault() there is ignored and the panel
  // scrolls under the cursor instead of the value changing. Bind it natively.
  useEffect(() => {
    const el = ref.current;
    if (!el) return;
    const onWheel = (e: WheelEvent) => {
      e.preventDefault();
      const mag = e.ctrlKey || e.shiftKey ? 100 : 10;
      step.current(e.deltaY < 0 ? mag : -mag);
    };
    el.addEventListener('wheel', onWheel, { passive: false });
    return () => el.removeEventListener('wheel', onWheel);
  }, []);

  const onKey = (e: React.KeyboardEvent) => {
    const up = e.key === 'ArrowUp' || e.key === 'ArrowRight';
    const dn = e.key === 'ArrowDown' || e.key === 'ArrowLeft';
    if (!up && !dn) return;
    e.preventDefault();
    const mag = e.ctrlKey || e.shiftKey ? 100 : 10;
    step.current(up ? mag : -mag);
  };

  return (
    <div className="flex items-center gap-2" title={title}>
      <Chip on={on} onClick={onToggle} label={label} disabled={disabled} accent="cyan" />
      <div ref={ref} data-offsetrow tabIndex={disabled || !on ? -1 : 0} onKeyDown={onKey}
        className={cn('flex-1 flex items-center justify-between rounded-md border px-1 py-0.5 select-none',
          'focus:outline-none focus:ring-2 focus:ring-info/50',
          on && !disabled ? 'border-border bg-muted/40 cursor-ns-resize' : 'border-border/60 bg-muted/20 opacity-60')}>
        <button type="button" disabled={disabled || !on} onClick={() => step.current(-10)}
          className="px-2 text-sm font-bold text-muted-foreground hover:text-foreground disabled:hover:text-muted-foreground">−</button>
        <span className={cn('text-sm font-mono font-bold tabular-nums', on && hz ? 'text-info' : 'text-muted-foreground')}>
          {hz > 0 ? '+' : hz < 0 ? '−' : ''}{Math.abs(hz || 0)} Hz
        </span>
        <button type="button" disabled={disabled || !on} onClick={() => step.current(10)}
          className="px-2 text-sm font-bold text-muted-foreground hover:text-foreground disabled:hover:text-muted-foreground">+</button>
      </div>
      <button type="button" disabled={disabled || !hz} onClick={() => onHz(0)}
        className="w-8 shrink-0 py-1 rounded-md text-[11px] font-bold border border-border bg-card text-muted-foreground hover:bg-muted disabled:opacity-30">0</button>
    </div>
  );
}

// MeterBar — a segmented "LED" instrument bar (radio look) scaled by lo/hi.
// `display` overrides the numeric readout; `segColor` colours segments by their
// 0..1 position (zones); the top ~18% light red by default (overload/peak).
function Card({ icon: Icon, title, accent, children, ckey, open: openProp, onToggle }: {
  icon: any; title: string; accent?: string; children: React.ReactNode; ckey?: string;
  open?: boolean; onToggle?: () => void; // controlled mode — lets sibling cards share one collapse state
}) {
  // Collapsible: a chevron in the header hides the body. Uncontrolled by default,
  // persisting per card (keyed by ckey, falling back to the title); when `open`/
  // `onToggle` are supplied the parent owns the state (e.g. linked TX/RX cards).
  const storeKey = 'opslog.cardOpen.' + (ckey || title);
  const [openState, setOpenState] = useState(() => localStorage.getItem(storeKey) !== '0');
  const controlled = openProp !== undefined;
  const open = controlled ? openProp : openState;
  const toggle = controlled
    ? (onToggle ?? (() => {}))
    : () => setOpenState((o) => { const n = !o; localStorage.setItem(storeKey, n ? '1' : '0'); return n; });
  return (
    <div className="rounded-xl border border-border bg-card shadow-sm overflow-hidden">
      <button type="button" onClick={toggle}
        className={cn('w-full flex items-center gap-2 px-3 py-2 bg-muted/30 hover:bg-muted/50 transition-colors text-left', open && 'border-b border-border/60')}>
        <Icon className="size-4" style={{ color: accent ?? 'var(--primary)' }} />
        <span className="text-xs font-bold uppercase tracking-wider text-foreground/80">{title}</span>
        <ChevronDown className={cn('ml-auto size-4 text-muted-foreground transition-transform', !open && '-rotate-90')} />
      </button>
      {open && <div className="p-3 space-y-3">{children}</div>}
    </div>
  );
}

// speMaxW is the amp's rated output, used as the power-bar full scale. Derived from
// the model string the amp reports (13K→1.3kW, 15K→1.5kW, 2K/20K→2kW).
function speMaxW(model?: string): number {
  const m = (model || '').toUpperCase();
  if (m.includes('20K') || m.includes('2K')) return 2000;
  if (m.includes('15K')) return 1500;
  return 1300; // 1.3K-FA default
}

// powerLevelLabel spells out the SPE power-level code (L/M/H) in full.
function powerLevelLabel(pl?: string): string {
  switch ((pl || '').trim().toUpperCase()) {
    case 'L': return 'Low';
    case 'M': return 'Mid';
    case 'H': return 'High';
    default: return pl || '';
  }
}

// onCWSpeed (optional): notified when the operator changes CW speed here, so the
// host can keep the WinKeyer (which actually sends the macros) in sync.
export function FlexPanel({ onCWSpeed, onReportRST }: { onCWSpeed?: (wpm: number) => void; onReportRST?: (rst: string) => void } = {}) {
  const { t } = useI18n();
  // Split-pile-up chaser (see flexrstchase.go). Held here so the button can
  // paint its state without polling the backend for it.
  const [rstChase, setRstChase] = useState<{ enabled: boolean; markers: string; offset_hz: number; split_only: boolean }>(
    { enabled: false, markers: '599', offset_hz: 0, split_only: true });
  const [rstChaseOffsetText, setRstChaseOffsetText] = useState('0');
  // The marker text is edited as raw text and only parsed on blur: it is a
  // comma-separated list, and normalising it on every keystroke would eat the
  // comma the moment it is typed.
  const [rstChaseMarkersText, setRstChaseMarkersText] = useState('599');
  // The marker field is summoned by right-clicking the button, not parked on
  // the row: it is set once to agree with SDC and then never touched, while the
  // row it was sitting in is the busiest in the panel.
  const [rstChaseEditing, setRstChaseEditing] = useState(false);
  useEffect(() => {
    GetFlexRSTChase().then((c: any) => {
      if (!c) return;
      setRstChase(c); setRstChaseOffsetText(String(c.offset_hz ?? 0)); setRstChaseMarkersText(String(c.markers ?? '599'));
    }).catch(() => {});
  }, []);
  const [st, setSt] = useState<FlexState>(ZERO);
  // Extra/"advanced" DSP rows (WNB + the SmartSDR v4 NRL/NRS/NRF/ANFL/AI-FFT
  // block) collapse behind a button so the RECEIVE card doesn't grow tall — only
  // NB/NR/ANF stay visible by default. Remembered across sessions.
  const [dspOpen, setDspOpen] = useState<boolean>(() => localStorage.getItem('opslog.flexDspOpen') === '1');
  const toggleDsp = () => setDspOpen((o) => { const n = !o; localStorage.setItem('opslog.flexDspOpen', n ? '1' : '0'); return n; });
  const hold = useRef<Record<string, number>>({});
  // Keep the host's keyer speed (used for CW-length estimates and the keyer panel)
  // in step with the radio's ACTUAL CW speed — including when it's changed on the
  // radio / in SmartSDR, not just via the slider below. Notify only on a real change.
  const lastCwSpeed = useRef<number | null>(null);
  useEffect(() => {
    const w = st.cw_speed;
    if (typeof w === 'number' && w > 0 && w !== lastCwSpeed.current) {
      lastCwSpeed.current = w;
      onCWSpeed?.(w);
    }
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [st.cw_speed]);
  // Peak-hold: keep the highest reading for a second so the jittery VITA-49
  // meters read steadily instead of jumping every poll. One second, not two —
  // longer and the meters visibly trail the end of a transmission, which reads
  // as the app lagging rather than as a peak being held.
  const peak = useRef<Record<string, { v: number; t: number }>>({});
  const peakHold = (key: string, val: number, windowMs = 1000, live = true) => {
    // live=false throws the held value away at once (e.g. the carrier dropped),
    // so the meter falls immediately instead of coasting for the window.
    if (!live) { delete peak.current[key]; return val; }
    const now = Date.now();
    const p = peak.current[key];
    if (!p || val >= p.v || now - p.t > windowMs) { peak.current[key] = { v: val, t: now }; return val; }
    return p.v;
  };

  useEffect(() => {
    let alive = true;
    const tick = async () => {
      try {
        const s = (await GetFlexState()) as any as FlexState;
        if (!alive) return;
        setSt((prev) => {
          const now = Date.now();
          const merged: any = { ...s };
          for (const k in hold.current) {
            if (hold.current[k] > now) merged[k] = (prev as any)[k];
          }
          return merged as FlexState;
        });
      } catch { /* not connected */ }
    };
    tick();
    const id = window.setInterval(tick, 400);
    return () => { alive = false; window.clearInterval(id); };
  }, []);

  // PowerGenius XL direct connection (fan mode), independent of the Flex link.
  const [pg, setPg] = useState<{ connected: boolean; fan_mode?: string; host?: string; last_error?: string; state?: string; peak_w?: number }>({ connected: false });
  useEffect(() => {
    let alive = true;
    const tick = async () => { try { const s: any = await GetPGXLStatus(); if (alive) setPg(s || { connected: false }); } catch {} };
    tick();
    const id = window.setInterval(tick, 250); // match the amp's 250 ms poll so the card catches every envelope peak
    return () => { alive = false; window.clearInterval(id); };
  }, []);

  // TRANSMIT + RECEIVE share ONE collapse state (they sit side by side, so folding
  // one folds the other and keeps the row tidy). Persisted like the other cards.
  const [txrxOpen, setTxrxOpen] = useState(() => localStorage.getItem('opslog.cardOpen.txrx') !== '0');
  const toggleTxrx = () => setTxrxOpen((o) => { const n = !o; localStorage.setItem('opslog.cardOpen.txrx', n ? '1' : '0'); return n; });

  // Tuner Genius XL direct connection — its own card in the Flex panel (like PGXL).
  const [tg, setTg] = useState<TGStatus>({ connected: false });
  const [tgEnabled, setTgEnabled] = useState(false);
  useEffect(() => {
    let alive = true;
    const tick = async () => {
      try { const en: any = await GetTunerGeniusSettings(); if (alive) setTgEnabled(!!en?.enabled); } catch {}
      try { const s: any = await GetTunerGeniusStatus(); if (alive && s) setTg(s as TGStatus); } catch {}
    };
    tick();
    const id = window.setInterval(tick, 500); // fast so meters track TX (see App.tsx)
    return () => { alive = false; window.clearInterval(id); };
  }, []);

  // Configured amplifiers (Settings → Amplifier) — possibly SEVERAL (some ops
  // run two SPEs in parallel). The card shows ONE at a time; the dropdown picks
  // which, and the choice is remembered per panel.
  const [ampList, setAmpList] = useState<any[]>([]);
  const [ampSel, setAmpSel] = useState<string>(() => localStorage.getItem('opslog.ampSel.flex') || '');
  useEffect(() => {
    let alive = true;
    const tick = () => GetAmpStatuses().then((l: any) => alive && setAmpList((l ?? []) as any[])).catch(() => {});
    tick();
    const id = window.setInterval(tick, 1500);
    return () => { alive = false; window.clearInterval(id); };
  }, []);
  const selAmp = ampList.find((a: any) => a.id === ampSel) ?? ampList[0];
  const isSPE = !!selAmp?.spe;
  const isACOM = !!selAmp?.acom;
  const spe = selAmp?.spe ?? { connected: false };
  const acom = selAmp?.acom ?? { connected: false };
  const ampPicker = ampList.length > 1 && selAmp ? (
    <select value={selAmp.id}
      onChange={(e) => { setAmpSel(e.target.value); localStorage.setItem('opslog.ampSel.flex', e.target.value); }}
      title={t('flxp.ampPick')}
      className="h-8 rounded-md border border-border bg-card px-2 text-xs font-semibold outline-none">
      {ampList.map((a: any) => <option key={a.id} value={a.id}>{a.name}</option>)}
    </select>
  ) : null;

  const change = (key: keyof FlexState, val: number | boolean | string, send: () => Promise<any>) => {
    hold.current[key] = Date.now() + 900;
    setSt((p) => ({ ...p, [key]: val }));
    send().catch(() => {});
  };

  const off = !st.available;
  const rxOff = off || !st.rx_avail;

  // Radio-style global RIT tuning: Ctrl+←/→ nudges the RIT offset (Ctrl+Shift =
  // ±100 Hz) from ANYWHERE — including while the callsign/RST entry field has
  // focus, which is where the operator is while working a station. Only the RIT
  // offset row itself (data-offsetrow) is skipped, since it handles its own
  // arrows. Text-field word navigation via Ctrl+← / → is overridden only while
  // RIT is actually engaged (guarded below), which is fine mid-QSO. Requires RIT
  // on and the RX available.
  useEffect(() => {
    const onKey = (e: KeyboardEvent) => {
      if (!(e.ctrlKey || e.metaKey)) return;
      if (e.key !== 'ArrowLeft' && e.key !== 'ArrowRight') return;
      const ae = document.activeElement as HTMLElement | null;
      if (ae && ae.hasAttribute('data-offsetrow')) return;
      if (!st.rit || rxOff) return;
      e.preventDefault();
      const mag = e.shiftKey ? 100 : 10;
      const next = (st.rit_freq || 0) + (e.key === 'ArrowRight' ? mag : -mag);
      change('rit_freq', next, () => FlexSetRITFreq(next));
    };
    window.addEventListener('keydown', onKey);
    return () => window.removeEventListener('keydown', onKey);
  }, [st.rit, st.rit_freq, rxOff]);

  // Clear the RIT offset back to 0 whenever a QSO is logged (any path: Log
  // button, keyer macro, WSJT-X), so the next station starts centred. Only acts
  // when there's actually a non-zero offset to clear.
  useEffect(() => {
    const off = EventsOn('qso:logged', () => {
      if (st.rit && (st.rit_freq || 0) !== 0) change('rit_freq', 0, () => FlexSetRITFreq(0));
    });
    return () => off();
  }, [st.rit, st.rit_freq]);
  const isCW = (st.mode || '').toUpperCase().includes('CW');
  // Phone (voice) modes — MIC / COMP meters only make sense here, so they're
  // hidden in CW and digital.
  const isPhone = /\b(SSB|USB|LSB|AM|FM|DFM|NFM)\b/i.test(st.mode || '');
  const PROC = [{ v: '0', l: 'NOR' }, { v: '1', l: 'DX' }, { v: '2', l: 'DX+' }];
  const AGC = [{ v: 'off', l: 'OFF' }, { v: 'slow', l: 'SLOW' }, { v: 'med', l: 'MED' }, { v: 'fast', l: 'FAST' }];
  const CW_BW = [100, 200, 300, 400, 500];
  const SSB_BW = [1800, 2100, 2400, 2700, 3000, 4000, 6000];
  const curBW = Math.max(0, (st.filter_hi || 0) - (st.filter_lo || 0));
  // Highlight the preset CLOSEST to the radio's actual filter width. The rig
  // rarely reports a width that lands exactly on a preset (e.g. 2.7k presets as
  // 100–2790), so an exact/±50 match would leave nothing lit — "doesn't pick up
  // the current filter". Snapping to the nearest preset always reflects the rig.
  const ssbActiveBW = curBW > 0
    ? SSB_BW.reduce((best, bw) => (Math.abs(bw - curBW) < Math.abs(best - curBW) ? bw : best), SSB_BW[0])
    : -1;

  // Compact header readouts for PA voltage + temperature (numbers, not bars) — the
  // user wanted these in the header to save vertical space in the Meters card.
  const hdrMeters = st.meters || [];
  const hdrFind = (name: string) => hdrMeters.find((m) => (m.name || '').toUpperCase().includes(name) && !(m.src || '').toUpperCase().includes('AMP'));
  const voltM = hdrFind('13.8') || hdrFind('VOLT');
  const patempM = hdrFind('PATEMP') || hdrFind('PA TEMP');

  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 */}
        <div className="flex items-center gap-3 rounded-xl px-4 py-3 text-white shadow-sm"
          style={{ background: 'linear-gradient(135deg,#1e293b,#0f172a)' }}>
          <Radio className="size-6 text-sky-400" />
          <div className="flex flex-col leading-tight">
            <span className="text-base font-extrabold tracking-tight">{st.model || 'FlexRadio'}</span>
            <span className="text-[11px] text-slate-400">{t('flxp.smartsdrRemote')}</span>
          </div>
          <div className="flex-1" />
          {/* Compact PA voltage + temperature readouts (numbers) to save space. */}
          {!off && (voltM || patempM) && (
            <div className="flex items-center gap-4 mr-1 font-mono tabular-nums">
              {voltM && (
                <span className="flex flex-col items-end leading-none">
                  <span className="text-[9px] uppercase tracking-wider text-slate-400 font-sans">{t('flxp.voltage')}</span>
                  <span className="text-sm font-bold">{voltM.value.toFixed(1)}<span className="text-[9px] text-slate-400 ml-0.5">V</span></span>
                </span>
              )}
              {patempM && (
                <span className="flex flex-col items-end leading-none">
                  <span className="text-[9px] uppercase tracking-wider text-slate-400 font-sans">{t('flxp.paTemp')}</span>
                  <span className={cn('text-sm font-bold', patempM.value >= 70 ? 'text-red-400' : patempM.value >= 55 ? 'text-amber-400' : 'text-slate-100')}>{patempM.value.toFixed(0)}<span className="text-[9px] text-slate-400 ml-0.5">°C</span></span>
                </span>
              )}
            </div>
          )}
          <span className={cn('inline-flex items-center gap-1.5 px-3 py-1.5 rounded-lg text-sm font-extrabold tracking-wider',
            !st.available ? 'bg-muted text-muted-foreground'
              : st.transmitting ? 'bg-danger text-danger-foreground shadow-[0_0_16px] shadow-danger/60' : 'bg-success text-success-foreground')}>
            <span className="size-2 rounded-full bg-current" />
            {!st.available ? t('flxp.offline') : st.transmitting ? 'TX' : 'RX'}
          </span>
        </div>

        {/* Slices A/B/C/D — every in-use receiver. Click one to make it the
            ACTIVE slice: the main frequency, mode, DSP and spot-clicks all follow
            it. The active slice is highlighted; the TX slice is flagged. */}
        {!!st.slices && st.slices.length > 0 && (
          <div className="flex flex-wrap items-center gap-2">
            {st.slices.map((sl) => (
              <button key={sl.index} type="button" disabled={off}
                onClick={() => FlexSetActiveSlice(sl.index).catch(() => {})}
                title={t('flxp.sliceHint')}
                className={cn('flex items-center gap-2 rounded-lg border px-3 py-1.5 transition-colors disabled:opacity-40',
                  sl.active ? 'border-info bg-info/10 ring-1 ring-info' : 'border-border bg-card hover:bg-muted')}>
                <span className={cn('inline-flex size-6 items-center justify-center rounded-md text-sm font-extrabold',
                  sl.active ? 'bg-info text-info-foreground' : 'bg-muted text-muted-foreground')}>{sl.letter}</span>
                <span className="flex items-baseline gap-1.5">
                  <span className="font-mono font-bold tabular-nums text-sm leading-none">{(sl.freq_hz / 1e6).toFixed(3)}</span>
                  <span className="text-[11px] text-muted-foreground uppercase leading-none">{sl.mode}</span>
                  {sl.band ? <span className="text-[10px] text-muted-foreground leading-none">{sl.band}</span> : null}
                </span>
                {/* TX badge — click a non-TX slice's badge to move TX onto it
                    (e.g. transmit on the active slice). stopPropagation so it
                    doesn't also fire the outer "make active" click. */}
                <span role="button" tabIndex={-1}
                  onClick={(e) => { e.stopPropagation(); if (!sl.tx && !off) FlexSetTXSlice(sl.index).catch(() => {}); }}
                  title={sl.tx ? t('flxp.txSlice') : t('flxp.setTxSlice')}
                  className={cn('rounded text-[10px] font-bold px-1',
                    sl.tx ? 'bg-danger text-danger-foreground' : 'border border-danger/50 text-danger cursor-pointer hover:bg-danger/10')}>
                  TX
                </span>
              </button>
            ))}
          </div>
        )}

        {/* Live meters (UDP VITA-49 stream) — placed right under the slices. */}
        <Card icon={Gauge} title={t('flxp.meters')}>
          {(() => {
            const meters = st.meters || [];
            if (off || meters.length === 0) {
              return <p className="text-[11px] text-muted-foreground text-center py-2">{t('flxp.noMeters')}</p>;
            }
            const isDbm = (m?: Meter) => !!m && /dbm/i.test(m.unit || '');
            const dbmToW = (d: number) => Math.pow(10, (d - 30) / 10);
            // Radio meters (exclude the amplifier's, which we show separately).
            const radio = (name: string) => meters.find((m) =>
              (m.name || '').toUpperCase().includes(name) && !(m.src || '').toUpperCase().includes('AMP'));
            // Per-slice (SLC) meters — S-meter — exist once PER SLICE, so pick the
            // one for the ACTIVE slice; otherwise we'd always show slice A's level.
            const activeSlice = (st.slices || []).find((s) => s.active)?.index ?? -1;
            const sliceMeter = (name: string) => {
              const m = meters.filter((x) => (x.name || '').toUpperCase().includes(name) && !(x.src || '').toUpperCase().includes('AMP'));
              if (m.length === 0) return undefined;
              return m.find((x) => (x.src || '').toUpperCase().includes('SLC') && x.slice === activeSlice) || m[0];
            };
            const sig = sliceMeter('LEVEL') || sliceMeter('SIGNAL');
            const fwd = radio('FWDPWR');
            const swr = radio('SWR');
            // Mic input level + speech-compression (voltage & PA temp live in the
            // header now). All already in the meter stream.
            const mic = meters.find((m) => /MIC/i.test(m.name || '') && !(m.src || '').toUpperCase().includes('AMP'));
            const comp = radio('COMPPEAK') || radio('COMP');
            // S-meter: dBm → S-units (S9 = -73 dBm on HF, 6 dB per unit).
            const sUnit = (dbm: number) => {
              const sv = (dbm + 127) / 6; // S0 = -127 dBm
              if (sv >= 9) {
                const over = Math.max(0, Math.round(dbm + 73)); // dB over S9
                return { display: over > 0 ? `S9+${over}` : 'S9', bar: sv, s: 9, over };
              }
              return { display: `S${Math.max(0, Math.round(sv))}`, bar: Math.max(0, sv), s: Math.max(0, sv), over: 0 };
            };
            const cur = [
              sig && (() => { const dbm = peakHold('s', sig.value); const s = sUnit(dbm); return (
                // dBm sits inline next to the S-value (no separate line below) to save height.
                <MeterBar key="s" label="S-METER" value={s.bar} lo={0} hi={19} accent="#16a34a" display={`${s.display} | ${dbm.toFixed(0)} dBm`}
                  title={onReportRST ? t('rst.clickToFill') : undefined}
                  onClick={onReportRST ? () => onReportRST(sMeterRST(s.s, s.over, st.mode)) : undefined}
                  segColor={(fr) => { const sval = fr * 19; return sval < 9 ? '#16a34a' : sval < 12.33 ? '#f59e0b' : '#dc2626'; }} />
              ); })(),
              fwd && (() => { const w = peakHold('p', isDbm(fwd) ? dbmToW(fwd.value) : fwd.value); return (
                <MeterBar key="p" label="PWR" unit="W" lo={0} hi={120} accent="#dc2626" value={w} />
              ); })(),
              swr && <MeterBar key="w" label="SWR" value={peakHold('w', swr.value)} unit="" lo={1} hi={3} accent="#d97706" />,
              // Mic input level in dBFS — SmartSDR's scale is -40…0 dB. Phone modes only.
              isPhone && mic && <MeterBar key="mic" label="MIC" value={peakHold('mic', mic.value)} unit={mic.unit || 'dB'} lo={-40} hi={0} accent="#16a34a"
                segColor={(fr) => (fr >= 0.8 ? '#dc2626' : fr >= 0.7 ? '#f59e0b' : '#16a34a')} />,
              // Speech compression — original working meter, only the top of the
              // scale changed from the radio-reported 20 to 25 (SmartSDR's -25 max).
              isPhone && comp && <MeterBar key="comp" label="COMP" value={peakHold('comp', comp.value)} unit={comp.unit || 'dB'} lo={0} hi={25} accent="#0891b2" />,
            ].filter(Boolean);
            return (
              <div className="grid grid-cols-2 sm:grid-cols-3 gap-2">{cur}</div>
            );
          })()}
        </Card>

        {off && (
          <div className="text-center text-sm text-muted-foreground py-6">
            {t('flxp.waiting')}
          </div>
        )}

        {/* TX + RX columns */}
        <div className="grid grid-cols-1 lg:grid-cols-2 gap-3">
          {/* TRANSMIT */}
          <Card icon={Zap} title={t('flxp.transmit')} accent="#dc2626" open={txrxOpen} onToggle={toggleTxrx}>
            <div className="flex items-center gap-3">
              <span className="w-20 shrink-0 text-xs font-medium text-muted-foreground">{t('flxp.rfPower')}</span>
              <Slider value={st.rf_power} disabled={off} accent="#dc2626" onChange={(v) => change('rf_power', v, () => FlexSetPower(v))} />
              <span className="w-9 text-right text-sm font-mono font-bold tabular-nums">{st.rf_power}</span>
            </div>
            <div className="flex items-center gap-3">
              <span className="w-20 shrink-0 text-xs font-medium text-muted-foreground">{t('flxp.tunePwr')}</span>
              <Slider value={st.tune_power} disabled={off} accent="#d97706" onChange={(v) => change('tune_power', v, () => FlexSetTunePower(v))} />
              <span className="w-9 text-right text-sm font-mono font-bold tabular-nums">{st.tune_power}</span>
            </div>
            <div className="flex items-center gap-2 pt-0.5">
              <button type="button" disabled={off}
                onClick={() => change('tune', !st.tune, () => FlexTune(!st.tune))}
                className={cn('flex-1 px-3 py-2.5 rounded-lg text-sm font-extrabold tracking-wide border-2 transition-all disabled:opacity-30',
                  st.tune ? 'bg-warning text-warning-foreground border-warning shadow-[0_0_14px] shadow-warning/50' : 'bg-card text-warning border-warning hover:bg-warning-muted')}>
                TUNE
              </button>
              <button type="button" disabled={off}
                onClick={() => change('transmitting', !st.transmitting, () => FlexMox(!st.transmitting))}
                className={cn('flex-1 px-3 py-2.5 rounded-lg text-sm font-extrabold tracking-wide border-2 transition-all disabled:opacity-30',
                  st.transmitting ? 'bg-danger text-danger-foreground border-danger shadow-[0_0_14px] shadow-danger/50' : 'bg-card text-danger border-danger hover:bg-danger-muted')}>
                <Power className="size-4 inline mr-1 -mt-0.5" /> MOX
              </button>
            </div>
            <div className="flex items-center gap-2">
              <button type="button" disabled={off}
                title={t('flxp.splitHint')}
                onClick={() => change('split', !st.split, () => FlexSetSplit(!st.split))}
                className={cn('px-3 py-1.5 rounded-lg text-sm font-extrabold tracking-wide border-2 transition-all disabled:opacity-30',
                  st.split ? 'bg-info text-info-foreground border-info shadow-[0_0_12px] shadow-info/50' : 'bg-card text-info border-info hover:bg-info-muted')}>
                SPLIT
              </button>
              {/* The split chaser. On the panel and not in a settings page:
                  it is switched on when a DXpedition appears and off when it is
                  in the log, which is done mid-QSO with one hand. The offset
                  sits beside it because it is the one number retuned while
                  chasing — everything else about the feature is configured once. */}
              {/* CW ONLY. The marker comes from a skimmer decoding a CW report,
                  so on SSB or FT8 there is nothing to chase — and a switch that
                  cannot fire is an invitation to wonder why it does nothing. */}
              {isCW && (<>
              <button type="button" disabled={off}
                title={t('flxp.rstChaseHint', { m: rstChase.markers })}
                onContextMenu={(e) => { e.preventDefault(); setRstChaseEditing(true); }}
                onClick={() => { const on = !rstChase.enabled; setRstChase({ ...rstChase, enabled: on }); SetFlexRSTChaseEnabled(on).catch(() => {}); }}
                className={cn('px-3 py-1.5 rounded-lg text-sm font-extrabold tracking-wide border-2 transition-all disabled:opacity-30',
                  rstChase.enabled ? 'bg-success text-success-foreground border-success shadow-[0_0_12px] shadow-success/50' : 'bg-card text-success border-success hover:bg-success-muted')}>
                {rstChase.markers.split(',')[0].trim() || '599'}
              </button>
              {/* The marker is whatever SDC was told to write — "599", "5NN", or
                  several. It has to agree with the skimmer or nothing ever fires,
                  so it is edited here rather than in a settings page — but only
                  when asked for, since it is set once and then left alone. */}
              {rstChaseEditing && (
                <input type="text" autoFocus value={rstChaseMarkersText}
                  onChange={(e) => setRstChaseMarkersText(e.target.value)}
                  onKeyDown={(e) => {
                    if (e.key === 'Enter') (e.target as HTMLInputElement).blur();
                    if (e.key === 'Escape') { setRstChaseMarkersText(rstChase.markers); setRstChaseEditing(false); }
                  }}
                  onBlur={() => {
                    const v = rstChaseMarkersText.trim() || '599';
                    setRstChaseMarkersText(v);
                    setRstChaseEditing(false);
                    if (v === rstChase.markers) return;
                    const next = { ...rstChase, markers: v };
                    setRstChase(next); SaveFlexRSTChase(next as any).catch(() => {});
                  }}
                  title={t('flxp.rstChaseMarkerHint')}
                  className="w-16 h-6 rounded border border-input bg-background px-1 text-[11px] font-mono uppercase" />
              )}
              {rstChase.enabled && (
                <label className="flex items-center gap-1 text-[11px] text-muted-foreground whitespace-nowrap ml-2">
                  {t('flxp.rstChaseOffset')}
                  <input type="number" step={10} value={rstChaseOffsetText}
                    onChange={(e) => setRstChaseOffsetText(e.target.value)}
                    onBlur={() => {
                      // Kept as text while typing: normalising every keystroke
                      // makes a minus sign impossible to enter.
                      const n = Math.round(Number(rstChaseOffsetText));
                      const v = Number.isFinite(n) ? n : 0;
                      setRstChaseOffsetText(String(v));
                      const next = { ...rstChase, offset_hz: v };
                      setRstChase(next); SaveFlexRSTChase(next as any).catch(() => {});
                    }}
                    className="w-16 h-6 rounded border border-input bg-background px-1.5 text-[11px] font-mono" />
                  Hz
                </label>
              )}
              </>)}
              {st.split && !!st.tx_freq_hz && (
                <span className="text-[11px] font-mono text-muted-foreground whitespace-nowrap">
                  TX {(st.tx_freq_hz / 1e6).toFixed(3)}
                  {!!st.rx_freq_hz && ` (${(st.tx_freq_hz - st.rx_freq_hz) >= 0 ? '+' : ''}${((st.tx_freq_hz - st.rx_freq_hz) / 1000).toFixed(1)} kHz)`}
                </span>
              )}
            </div>

            {/* ATU — the radio's built-in tuner. Sits under TUNE because that is
                the order of operations: the tuner needs a carrier to measure
                against, so TUNE first, then start a tuning cycle. */}
            <div className="flex items-center gap-2 border-t border-border/60 pt-3">
              <span className="w-14 shrink-0 text-[11px] font-bold text-muted-foreground">ATU</span>
              <button type="button" disabled={off}
                title={t('flxp.atuTuneHint')}
                onClick={() => FlexATUStart().catch(() => {})}
                className="px-3 py-1.5 rounded-lg text-xs font-extrabold tracking-wide border-2 border-warning text-warning bg-card hover:bg-warning-muted transition-all disabled:opacity-30">
                {t('flxp.atuTune')}
              </button>
              <button type="button" disabled={off}
                title={t('flxp.atuBypassHint')}
                onClick={() => FlexATUBypass().catch(() => {})}
                className="px-3 py-1.5 rounded-lg text-xs font-extrabold tracking-wide border-2 border-border text-muted-foreground bg-card hover:bg-muted transition-all disabled:opacity-30">
                {t('flxp.atuBypass')}
              </button>
              <Chip on={st.atu_memories} disabled={off} label={t('flxp.atuMem')} accent="cyan"
                title={t('flxp.atuMemHint')}
                onClick={() => change('atu_memories', !st.atu_memories, () => FlexSetATUMemories(!st.atu_memories))} />
              {/* The status is the whole point: a tune that FAILED and one that
                  never ran look identical on the radio's own front panel. */}
              <span className={cn('ml-auto text-[11px] font-mono font-semibold whitespace-nowrap', atuTone(st.atu_status))}>
                {atuLabel(st.atu_status, t)}
              </span>
            </div>


            {!isCW ? (
            <div className="border-t border-border/60 pt-3 space-y-3">
              <div className="flex items-center gap-2">
                <Chip on={st.proc_enable} disabled={off} label="PROC" accent="violet"
                  onClick={() => change('proc_enable', !st.proc_enable, () => FlexSetProcessor(!st.proc_enable))} />
                <Segmented value={String(st.proc_level)} options={PROC} disabled={off || !st.proc_enable}
                  onChange={(v) => change('proc_level', parseInt(v, 10), () => FlexSetProcessorLevel(parseInt(v, 10)))} />
                <span className="flex-1" />
              </div>
              <LevelRow label="VOX" on={st.vox_enable} disabled={off} value={st.vox_level} sliderAccent="#16a34a"
                onToggle={() => change('vox_enable', !st.vox_enable, () => FlexSetVox(!st.vox_enable))}
                onLevel={(v) => change('vox_level', v, () => FlexSetVoxLevel(v))} />
              <div className="flex items-center gap-2">
                <span className="w-14 shrink-0 text-[11px] font-bold text-muted-foreground pl-0.5">{t('flxp.voxDly')}</span>
                <Slider value={st.vox_delay} disabled={off || !st.vox_enable} accent="#16a34a"
                  onChange={(v) => change('vox_delay', v, () => FlexSetVoxDelay(v))} />
                <span className="w-8 text-right text-xs font-mono tabular-nums text-muted-foreground">{st.vox_delay}</span>
              </div>
              <LevelRow label="MON" on={st.mon} disabled={off} value={st.mon_level} accent="cyan" sliderAccent="#0891b2"
                onToggle={() => change('mon', !st.mon, () => FlexSetMon(!st.mon))}
                onLevel={(v) => change('mon_level', v, () => FlexSetMonLevel(v))} />
              <div className="flex items-center gap-2">
                <span className="w-14 shrink-0 text-[11px] font-bold text-muted-foreground">MIC</span>
                <Slider value={st.mic_level} disabled={off} accent="#2563eb" onChange={(v) => change('mic_level', v, () => FlexSetMic(v))} />
                <span className="w-8 text-right text-xs font-mono tabular-nums text-muted-foreground">{st.mic_level}</span>
              </div>
              {/* TX audio bandwidth — low/high cut (Hz). Typed locally, committed on
                  blur/Enter so we don't spam a command per keystroke; the hold guard
                  keeps the poll from clobbering the field mid-edit. */}
              <div className="flex items-center gap-2">
                <span className="w-14 shrink-0 text-[11px] font-bold text-muted-foreground">{t('flxp.txFilter')}</span>
                {(() => {
                  const editTX = (patch: Partial<FlexState>) => { hold.current['tx_filter_low'] = Date.now() + 8000; hold.current['tx_filter_high'] = Date.now() + 8000; setSt((p) => ({ ...p, ...patch })); };
                  const commitTX = () => { hold.current['tx_filter_low'] = Date.now() + 900; hold.current['tx_filter_high'] = Date.now() + 900; FlexSetTXFilter(st.tx_filter_low || 0, st.tx_filter_high || 0).catch(() => {}); };
                  const inp = 'w-[70px] h-8 rounded-md border border-input bg-background px-2 text-xs font-mono tabular-nums disabled:opacity-40';
                  return (<>
                    <input type="number" step={50} min={0} disabled={off} value={st.tx_filter_low || 0} className={inp}
                      onChange={(e) => editTX({ tx_filter_low: parseInt(e.target.value, 10) || 0 })}
                      onBlur={commitTX} onKeyDown={(e) => { if (e.key === 'Enter') { (e.target as HTMLInputElement).blur(); } }} />
                    <span className="text-muted-foreground text-xs">–</span>
                    <input type="number" step={50} min={0} disabled={off} value={st.tx_filter_high || 0} className={inp}
                      onChange={(e) => editTX({ tx_filter_high: parseInt(e.target.value, 10) || 0 })}
                      onBlur={commitTX} onKeyDown={(e) => { if (e.key === 'Enter') { (e.target as HTMLInputElement).blur(); } }} />
                    <span className="text-[10px] text-muted-foreground/70 font-mono">Hz</span>
                  </>);
                })()}
              </div>
              {/* Mic profile — SmartSDR named mic-EQ/processor presets. */}
              {!!st.mic_profiles && st.mic_profiles.length > 0 && (
                <div className="flex items-center gap-2">
                  <span className="w-14 shrink-0 text-[11px] font-bold text-muted-foreground">{t('flxp.micProfile')}</span>
                  <select disabled={off} value={st.mic_profile || ''}
                    onChange={(e) => { const v = e.target.value; hold.current['mic_profile'] = Date.now() + 2000; setSt((p) => ({ ...p, mic_profile: v })); FlexSetMicProfile(v).catch(() => {}); }}
                    className="flex-1 min-w-0 h-8 rounded-md border border-input bg-background px-2 text-xs">
                    {!st.mic_profiles.includes(st.mic_profile || '') && <option value="">—</option>}
                    {st.mic_profiles.map((p) => <option key={p} value={p}>{p}</option>)}
                  </select>
                </div>
              )}
            </div>
            ) : (
            /* CW keyer controls (replace VOX/PROC/MIC when the slice is in CW). */
            <div className="border-t border-border/60 pt-3 space-y-3">
              <div className="flex items-center gap-2">
                <span className="w-16 shrink-0 text-[11px] font-bold text-muted-foreground">{t('flxp.speed')}</span>
                <Slider value={st.cw_speed} disabled={off} max={60} accent="#0d9488" onChange={(v) => { change('cw_speed', v, () => FlexSetCWSpeed(v)); onCWSpeed?.(v); }} />
                <span className="w-12 text-right text-xs font-mono tabular-nums text-muted-foreground">{st.cw_speed} wpm</span>
              </div>
              <div className="flex items-center gap-2">
                <span className="w-16 shrink-0 text-[11px] font-bold text-muted-foreground">{t('flxp.pitch')}</span>
                <Slider value={st.cw_pitch} disabled={off} max={1000} step={10} accent="#7c3aed" onChange={(v) => change('cw_pitch', v, () => FlexSetCWPitch(v))} />
                <span className="w-12 text-right text-xs font-mono tabular-nums text-muted-foreground">{st.cw_pitch} Hz</span>
              </div>
              <div className="flex items-center gap-2">
                <span className="w-16 shrink-0 text-[11px] font-bold text-muted-foreground">{t('flxp.delay')}</span>
                <Slider value={st.cw_break_in_delay} disabled={off} max={1000} step={1} accent="#d97706" onChange={(v) => change('cw_break_in_delay', v, () => FlexSetCWBreakInDelay(v))} />
                <span className="w-12 text-right text-xs font-mono tabular-nums text-muted-foreground">{st.cw_break_in_delay} ms</span>
              </div>
              <LevelRow label="STONE" on={st.cw_sidetone} disabled={off} value={st.cw_mon_level} accent="cyan" sliderAccent="#0891b2"
                onToggle={() => change('cw_sidetone', !st.cw_sidetone, () => FlexSetCWSidetone(!st.cw_sidetone))}
                onLevel={(v) => change('cw_mon_level', v, () => FlexSetSidetoneLevel(v))} />
            </div>
            )}

            {/* RIT/XIT live on the TRANSMIT side to balance the two columns — the
                RECEIVE card grew tall with the v4 DSP rows. */}
            <div className="space-y-1.5 border-t border-border/60 pt-3">
              <OffsetRow label="RIT" on={st.rit} disabled={rxOff} hz={st.rit_freq} title={t('flxp.ritHint')}
                onToggle={() => change('rit', !st.rit, () => FlexSetRIT(!st.rit))}
                onHz={(v) => change('rit_freq', v, () => FlexSetRITFreq(v))} />
              <OffsetRow label="XIT" on={st.xit} disabled={rxOff} hz={st.xit_freq} title={t('flxp.xitHint')}
                onToggle={() => change('xit', !st.xit, () => FlexSetXIT(!st.xit))}
                onHz={(v) => change('xit_freq', v, () => FlexSetXITFreq(v))} />
            </div>
          </Card>

          {/* RECEIVE */}
          <Card icon={AudioLines} title={t('flxp.receiveActive')} accent="#0891b2" open={txrxOpen} onToggle={toggleTxrx}>
            {/* Antenna selection sits at the very top of the RX column. */}
            {((st.ant_list?.length ?? 0) > 0 || (st.tx_ant_list?.length ?? 0) > 0) && (
              <div className="flex items-center gap-2 pb-3 border-b border-border/60">
                <span className="w-14 shrink-0 text-[11px] font-bold text-muted-foreground">Ant</span>
                <div className="flex items-center gap-1.5 flex-1 min-w-0">
                  <span className="text-[10px] text-muted-foreground">RX</span>
                  <select disabled={rxOff} value={st.rx_ant ?? ''}
                    onChange={(e) => change('rx_ant', e.target.value, () => FlexSetRXAntenna(e.target.value))}
                    className="h-7 flex-1 min-w-0 rounded-md border border-input bg-background px-1.5 text-xs font-mono disabled:opacity-40">
                    {(st.ant_list ?? []).map((a) => <option key={a} value={a}>{a}</option>)}
                  </select>
                  <span className="text-[10px] text-muted-foreground">TX</span>
                  <select disabled={rxOff} value={st.tx_ant ?? ''}
                    onChange={(e) => change('tx_ant', e.target.value, () => FlexSetTXAntenna(e.target.value))}
                    className="h-7 flex-1 min-w-0 rounded-md border border-input bg-background px-1.5 text-xs font-mono disabled:opacity-40">
                    {((st.tx_ant_list?.length ? st.tx_ant_list : st.ant_list) ?? []).map((a) => <option key={a} value={a}>{a}</option>)}
                  </select>
                  {/* DAX on/off — SmartSDR's transmit-bar DAX button (TX audio from
                      DAX, e.g. WSJT-X). "transmit set dax=", not the slice channel. */}
                  <button type="button" disabled={off} title={t('flxp.daxHint')}
                    onClick={() => change('tx_dax', !st.tx_dax, () => FlexSetTXDAX(!st.tx_dax))}
                    className={cn('h-7 px-2.5 shrink-0 rounded-md border text-[11px] font-bold transition-colors disabled:opacity-40',
                      st.tx_dax ? 'bg-info text-info-foreground border-info' : 'bg-card text-muted-foreground border-border hover:bg-muted')}>
                    DAX
                  </button>
                </div>
              </div>
            )}
            <div className="flex items-center gap-2">
              <span className="w-14 shrink-0 text-[11px] font-bold text-muted-foreground">AGC</span>
              <Segmented value={(st.agc_mode || 'med').toLowerCase()} options={AGC} disabled={rxOff}
                onChange={(v) => change('agc_mode', v, () => FlexSetAGCMode(v))} />
            </div>
            <div className="flex items-center gap-2">
              <span className="w-14 shrink-0 text-[11px] font-bold text-muted-foreground">AF</span>
              <button type="button" disabled={rxOff}
                title={st.mute ? t('flxp.muted') : t('flxp.mute')}
                onClick={() => change('mute', !st.mute, () => FlexSetMute(!st.mute))}
                className={cn('shrink-0 rounded p-1 transition-colors disabled:opacity-30',
                  st.mute ? 'bg-destructive text-destructive-foreground' : 'text-muted-foreground hover:bg-muted')}>
                {st.mute ? <VolumeX className="size-3.5" /> : <Volume2 className="size-3.5" />}
              </button>
              <Slider value={st.audio_level} disabled={rxOff || st.mute} accent="#16a34a" onChange={(v) => change('audio_level', v, () => FlexSetAudioLevel(v))} />
              <span className="w-8 text-right text-xs font-mono tabular-nums text-muted-foreground">{st.mute ? '—' : st.audio_level}</span>
            </div>
            <div className="flex items-center gap-2">
              <span className="w-14 shrink-0 text-[11px] font-bold text-muted-foreground">AGC-T</span>
              <Slider value={st.agc_threshold} disabled={rxOff} accent="#64748b" onChange={(v) => change('agc_threshold', v, () => FlexSetAGCThreshold(v))} />
              <span className="w-8 text-right text-xs font-mono tabular-nums text-muted-foreground">{st.agc_threshold}</span>
            </div>
            <div className="border-t border-border/60 pt-3 space-y-3">
              {/* Core noise controls stay visible; WNB + the SmartSDR v4 DSP block
                  hide behind the 'DSP' toggle so the card doesn't tower. When any
                  hidden control is ON we badge the toggle so it's not forgotten. */}
              {(() => {
                const hiddenOn = st.wnb || (st.dsp_v4 && (!!st.lms_nr || !!st.speex_nr || !!st.nrf || (!isCW && !!st.lms_anf) || !!st.rnn || (!isCW && !!st.anft)));
                return (
                  <div className="flex items-center justify-between">
                    <span className="text-[11px] font-bold text-muted-foreground">{t('flxp.dspNoise')}</span>
                    <button type="button" onClick={toggleDsp} title={t('flxp.dspMore')}
                      className={cn('flex items-center gap-1 h-6 px-2 rounded-md border text-[10px] font-bold uppercase tracking-wide transition-colors',
                        hiddenOn && !dspOpen ? 'bg-primary/15 text-primary border-primary/40' : 'bg-card text-muted-foreground border-border hover:bg-muted')}>
                      <SlidersHorizontal className="size-3" />
                      DSP
                      {hiddenOn && !dspOpen && <span className="ml-0.5 size-1.5 rounded-full bg-primary" />}
                      <ChevronDown className={cn('size-3 transition-transform', dspOpen && 'rotate-180')} />
                    </button>
                  </div>
                );
              })()}
              <LevelRow label="NB" on={st.nb} disabled={rxOff} value={st.nb_level} accent="amber" sliderAccent="#d97706"
                onToggle={() => change('nb', !st.nb, () => FlexSetNB(!st.nb))}
                onLevel={(v) => change('nb_level', v, () => FlexSetNBLevel(v))} />
              <LevelRow label="NR" on={st.nr} disabled={rxOff} value={st.nr_level} accent="cyan" sliderAccent="#0891b2"
                onToggle={() => change('nr', !st.nr, () => FlexSetNR(!st.nr))}
                onLevel={(v) => change('nr_level', v, () => FlexSetNRLevel(v))} />
              {/* ANF (auto notch) is for carriers in voice — meaningless on a CW tone, so hide it in CW. */}
              {!isCW && (
                <LevelRow label="ANF" on={st.anf} disabled={rxOff} value={st.anf_level} accent="violet" sliderAccent="#7c3aed"
                  onToggle={() => change('anf', !st.anf, () => FlexSetANF(!st.anf))}
                  onLevel={(v) => change('anf_level', v, () => FlexSetANFLevel(v))} />
              )}
              {/* Advanced / supplementary DSP — collapsed by default. WNB (wideband
                  noise blanker) plus the SmartSDR v4 block (8000/Aurora): NRL/ANFL
                  (legacy LMS), NRS (spectral subtraction), NRF (NR w/ filter), RNN
                  (AI NR), ANFT (FFT notch). The v4 rows only exist when the radio
                  reports those slice keys (older 6000s never do). */}
              {dspOpen && (
                <div className="space-y-3 border-t border-dashed border-border/50 pt-3">
                  {/* WNB — wideband noise blanker (the extra hardware NR the Flex has). */}
                  <LevelRow label="WNB" on={st.wnb} disabled={rxOff} value={st.wnb_level} accent="amber" sliderAccent="#f59e0b"
                    onToggle={() => change('wnb', !st.wnb, () => FlexSetWNB(!st.wnb))}
                    onLevel={(v) => change('wnb_level', v, () => FlexSetWNBLevel(v))} />
                  {st.dsp_v4 && (
                    <>
                      <LevelRow label="NRL" on={!!st.lms_nr} disabled={rxOff} value={st.lms_nr_level ?? 0} accent="cyan" sliderAccent="#0e7490"
                        onToggle={() => change('lms_nr', !st.lms_nr, () => FlexSetLMSNR(!st.lms_nr))}
                        onLevel={(v) => change('lms_nr_level', v, () => FlexSetLMSNRLevel(v))} />
                      <LevelRow label="NRS" on={!!st.speex_nr} disabled={rxOff} value={st.speex_nr_level ?? 0} accent="cyan" sliderAccent="#06b6d4"
                        onToggle={() => change('speex_nr', !st.speex_nr, () => FlexSetSpeexNR(!st.speex_nr))}
                        onLevel={(v) => change('speex_nr_level', v, () => FlexSetSpeexNRLevel(v))} />
                      <LevelRow label="NRF" on={!!st.nrf} disabled={rxOff} value={st.nrf_level ?? 0} accent="cyan" sliderAccent="#0369a1"
                        onToggle={() => change('nrf', !st.nrf, () => FlexSetNRF(!st.nrf))}
                        onLevel={(v) => change('nrf_level', v, () => FlexSetNRFLevel(v))} />
                      {/* Notch filters target carriers in voice — hidden in CW like ANF. */}
                      {!isCW && (
                        <LevelRow label="ANFL" on={!!st.lms_anf} disabled={rxOff} value={st.lms_anf_level ?? 0} accent="violet" sliderAccent="#8b5cf6"
                          onToggle={() => change('lms_anf', !st.lms_anf, () => FlexSetLMSANF(!st.lms_anf))}
                          onLevel={(v) => change('lms_anf_level', v, () => FlexSetLMSANFLevel(v))} />
                      )}
                      {/* RNN and ANFT are on/off only — no level in the API. */}
                      <div className="flex items-center gap-2">
                        <span className="w-14 shrink-0 text-[11px] font-bold text-muted-foreground" title={t('flxp.dspV4Hint')}>AI/FFT</span>
                        <button type="button" disabled={rxOff}
                          title={t('flxp.rnnHint')}
                          onClick={() => change('rnn', !st.rnn, () => FlexSetRNN(!st.rnn))}
                          className={cn('px-2.5 py-1 rounded-md border text-[11px] font-bold transition-colors disabled:opacity-30',
                            st.rnn ? 'bg-primary text-primary-foreground border-primary' : 'bg-card text-muted-foreground border-border hover:bg-muted')}>
                          RNN
                        </button>
                        {!isCW && (
                          <button type="button" disabled={rxOff}
                            title={t('flxp.anftHint')}
                            onClick={() => change('anft', !st.anft, () => FlexSetANFT(!st.anft))}
                            className={cn('px-2.5 py-1 rounded-md border text-[11px] font-bold transition-colors disabled:opacity-30',
                              st.anft ? 'bg-primary text-primary-foreground border-primary' : 'bg-card text-muted-foreground border-border hover:bg-muted')}>
                            ANFT
                          </button>
                        )}
                      </div>
                    </>
                  )}
                </div>
              )}
            </div>
            {isCW && (
              <div className="border-t border-border/60 pt-3 space-y-3">
                <LevelRow label="APF" on={st.apf} disabled={rxOff} value={st.apf_level} accent="emerald" sliderAccent="#16a34a"
                  onToggle={() => change('apf', !st.apf, () => FlexSetAPF(!st.apf))}
                  onLevel={(v) => change('apf_level', v, () => FlexSetAPFLevel(v))} />
                <div className="flex items-center gap-2">
                  <span className="w-14 shrink-0 text-[11px] font-bold text-muted-foreground">{t('flxp.filter')}</span>
                  <div className="inline-flex rounded-md border border-border overflow-hidden">
                    {CW_BW.map((bw) => (
                      <button key={bw} type="button" disabled={rxOff}
                        onClick={() => { setSt((p) => { const c = ((p.filter_lo || 0) + (p.filter_hi || 0)) ? Math.round(((p.filter_lo || 0) + (p.filter_hi || 0)) / 2) : (p.cw_pitch || 600); return { ...p, filter_lo: c - bw / 2, filter_hi: c + bw / 2 }; }); FlexSetCWFilter(bw).catch(() => {}); }}
                        className={cn('px-2 py-1 text-[11px] font-bold tracking-wide transition-colors disabled:opacity-30 border-l border-border first:border-l-0',
                          Math.abs(curBW - bw) <= 1 ? 'bg-primary text-primary-foreground' : 'bg-card text-muted-foreground hover:bg-muted')}>
                        {bw}
                      </button>
                    ))}
                  </div>
                  <span className="text-[10px] text-muted-foreground/70 font-mono">Hz</span>
                </div>
              </div>
            )}
            {!isCW && (
              <div className="border-t border-border/60 pt-3">
                <div className="flex items-center gap-2">
                  <span className="w-14 shrink-0 text-[11px] font-bold text-muted-foreground">{t('flxp.filter')}</span>
                  <div className="inline-flex rounded-md border border-border overflow-hidden">
                    {SSB_BW.map((bw) => (
                      <button key={bw} type="button" disabled={rxOff}
                        onClick={() => {
                          const lsb = (st.mode || '').toUpperCase().includes('LSB');
                          let lo: number, hi: number;
                          if (lsb) { const near = (st.filter_hi && st.filter_hi < 0) ? st.filter_hi : -100; hi = near; lo = near - bw; }
                          else { const near = (st.filter_lo && st.filter_lo > 0) ? st.filter_lo : 100; lo = near; hi = near + bw; }
                          setSt((p) => ({ ...p, filter_lo: lo, filter_hi: hi }));
                          FlexSetFilter(lo, hi).catch(() => {});
                        }}
                        className={cn('px-1.5 py-1 text-[11px] font-bold tracking-wide transition-colors disabled:opacity-30 border-l border-border first:border-l-0',
                          bw === ssbActiveBW ? 'bg-primary text-primary-foreground' : 'bg-card text-muted-foreground hover:bg-muted')}>
                        {(bw / 1000).toFixed(1)}
                      </button>
                    ))}
                  </div>
                  <span className="text-[10px] text-muted-foreground/70 font-mono">kHz</span>
                </div>
              </div>
            )}
          </Card>
        </div>

        {/* SPE Expert amplifier (serial/TCP) — shown when it's the configured amp.
            The Flex doesn't report SPE amps, so this card is driven by OpsLog's own
            SPE link rather than the Flex amplifier object. */}
        {isSPE && (
          <Card icon={Flame} ckey="amplifier" title={`${t('flxp.amplifier')} · ${selAmp?.name || `SPE${spe.model ? ' ' + spe.model : ''}`}`} accent="#ea580c">
            <div className="flex items-center gap-3 flex-wrap">
              {ampPicker}
              <button type="button" disabled={!spe.connected}
                onClick={() => AmpOperate(selAmp.id, !spe.operate).catch(() => {})}
                className={cn('px-4 py-2 rounded-lg text-sm font-extrabold tracking-wide border-2 transition-all disabled:opacity-30',
                  spe.operate ? 'bg-warning text-warning-foreground border-warning shadow-[0_0_14px] shadow-warning/50' : 'bg-card text-warning border-warning hover:bg-warning-muted')}>
                {spe.operate ? 'OPERATE' : 'STANDBY'}
              </button>
              {/* Power ON pulses RTS then DTR — it must stay clickable while the amp
                  is off (no status = not "connected"), and serial only. */}
              <div className="inline-flex rounded-lg overflow-hidden border-2 border-success/70">
                <button type="button" disabled={!(spe.connected || spe.transport === 'serial')}
                  onClick={() => AmpPower(selAmp.id, true).catch(() => {})}
                  title={spe.transport === 'serial' ? 'Power on (RTS then DTR pulse)' : 'Power-on needs the serial RTS/DTR lines — not available over a network bridge'}
                  className="px-3 py-2 text-sm font-bold bg-card text-success hover:bg-success/15 disabled:opacity-30">ON</button>
                <button type="button" disabled={!spe.connected}
                  onClick={() => AmpPower(selAmp.id, false).catch(() => {})}
                  className="px-3 py-2 text-sm font-bold bg-card text-danger border-l-2 border-success/70 hover:bg-danger/15 disabled:opacity-30">OFF</button>
              </div>
              {/* Output power level: Low / Mid / High. */}
              <div className="inline-flex rounded-lg overflow-hidden border border-border">
                {(['L', 'M', 'H'] as const).map((lvl, i) => {
                  const active = (spe.power_level || '').trim().toUpperCase() === lvl;
                  return (
                    <button key={lvl} type="button" disabled={!spe.connected}
                      onClick={() => AmpPowerLevel(selAmp.id, lvl).catch(() => {})}
                      className={cn('px-3 py-2 text-sm font-bold disabled:opacity-30', i > 0 && 'border-l border-border',
                        active ? 'bg-primary text-primary-foreground' : 'bg-card text-muted-foreground hover:bg-muted')}>
                      {powerLevelLabel(lvl)}
                    </button>
                  );
                })}
              </div>
              <span className={cn('inline-flex items-center gap-1.5 text-sm', spe.connected ? 'text-muted-foreground' : 'text-danger')}>
                <span className={cn('size-2 rounded-full', spe.connected ? 'bg-success' : 'bg-danger')} />
                {spe.connected ? (spe.tx ? 'TX' : 'RX') : t('flxp.speOffline')}
              </span>
              {spe.connected && (
                <span className="text-sm font-mono text-muted-foreground tabular-nums">
                  {spe.band ? `${spe.band} · ` : ''}{spe.output_w}W · SWR {Number(spe.swr_ant ?? 0).toFixed(1)} · {spe.temp_c}°C · {powerLevelLabel(spe.power_level)}
                </span>
              )}
              <div className="flex-1" />
              {(spe.warnings || spe.alarms) && (
                <span className="px-2 py-1 rounded bg-danger-muted text-danger-muted-foreground text-xs font-bold">⚠ {spe.warnings} {spe.alarms}</span>
              )}
            </div>
            {spe.connected && (
              <MeterBar label={t('flxp.outputPower')} value={Number(spe.output_w) || 0} unit="W"
                lo={0} hi={speMaxW(spe.model)}
                display={`${Number(spe.output_w) || 0} W`}
                segColor={(f) => (f > 0.9 ? '#dc2626' : f > 0.75 ? '#f59e0b' : '#ea580c')} />
            )}
          </Card>
        )}

        {/* ACOM amplifier (serial/TCP) — shown when it's the configured amp. Driven
            by OpsLog's own ACOM link (the Flex doesn't report ACOM amps). */}
        {isACOM && (
          <Card icon={Flame} ckey="amplifier" title={`${t('flxp.amplifier')} · ${selAmp?.name || `ACOM${acom.model ? ' ' + acom.model : ''}`}`} accent="#ea580c">
            <div className="flex items-center gap-3 flex-wrap">
              {ampPicker}
              <button type="button" disabled={!acom.connected}
                onClick={() => AmpOperate(selAmp.id, !acom.operate).catch(() => {})}
                className={cn('px-4 py-2 rounded-lg text-sm font-extrabold tracking-wide border-2 transition-all disabled:opacity-30',
                  acom.operate ? 'bg-warning text-warning-foreground border-warning shadow-[0_0_14px] shadow-warning/50' : 'bg-card text-warning border-warning hover:bg-warning-muted')}>
                {acom.operate ? 'OPERATE' : 'STANDBY'}
              </button>
              {/* Power ON is a DTR/RTS pulse — serial only, and it works while the amp
                  is off (the port stays open), so gate on port_open not connected. */}
              <div className="inline-flex rounded-lg overflow-hidden border-2 border-success/70">
                <button type="button" disabled={!(acom.port_open && acom.transport === 'serial')}
                  onClick={() => AmpPower(selAmp.id, true).catch(() => {})}
                  title={acom.transport === 'serial' ? 'Power on (DTR/RTS pulse — needs the power-on pins wired)' : 'Power-on needs the serial DTR/RTS lines — not available over a network bridge'}
                  className="px-3 py-2 text-sm font-bold bg-card text-success hover:bg-success/15 disabled:opacity-30">ON</button>
                <button type="button" disabled={!acom.connected}
                  onClick={() => AmpPower(selAmp.id, false).catch(() => {})}
                  className="px-3 py-2 text-sm font-bold bg-card text-danger border-l-2 border-success/70 hover:bg-danger/15 disabled:opacity-30">OFF</button>
              </div>
              <span className={cn('inline-flex items-center gap-1.5 text-sm', acom.connected ? 'text-muted-foreground' : 'text-danger')}>
                <span className={cn('size-2 rounded-full', acom.connected ? 'bg-success' : 'bg-danger')} />
                {acom.connected ? acom.state : t('flxp.acomOffline')}
              </span>
              {acom.connected && (
                <span className="text-sm font-mono text-muted-foreground tabular-nums">
                  {acom.band ? `${acom.band} · ` : ''}{acom.fwd_w}W · SWR {Number(acom.swr ?? 0).toFixed(1)} · {acom.temp_c}°C · Fan {acom.fan}
                </span>
              )}
              <div className="flex-1" />
              {acom.err_text && (
                <span className="px-2 py-1 rounded bg-danger-muted text-danger-muted-foreground text-xs font-bold">⚠ {acom.err_text}</span>
              )}
            </div>
            {acom.connected && (
              <MeterBar label={t('flxp.outputPower')} value={Number(acom.fwd_w) || 0} unit="W"
                lo={0} hi={Number(acom.max_w) || 800}
                display={`${Number(acom.fwd_w) || 0} W`}
                segColor={(f) => (f > 0.9 ? '#dc2626' : f > 0.75 ? '#f59e0b' : '#ea580c')} />
            )}
          </Card>
        )}

        {/* External amplifier (PowerGenius XL) — only when the Flex reports one AND
            PowerGenius is the selected amp type. Running an SPE Expert or ACOM hides
            this Flex-reported card so two amps don't both show. */}
        {st.amp_available && !isSPE && !isACOM && (
          <Card icon={Flame} ckey="amplifier" title={`${t('flxp.amplifier')}${st.amp_model ? ' · ' + st.amp_model : ''}`} accent="#ea580c">
            <div className="flex items-center gap-3">
              {ampPicker}
              <button type="button" disabled={off}
                onClick={() => change('amp_operate', !st.amp_operate, () => FlexAmpOperate(!st.amp_operate))}
                className={cn('px-4 py-2 rounded-lg text-sm font-extrabold tracking-wide border-2 transition-all disabled:opacity-30',
                  st.amp_operate ? 'bg-warning text-warning-foreground border-warning shadow-[0_0_14px] shadow-warning/50' : 'bg-card text-warning border-warning hover:bg-warning-muted')}>
                {st.amp_operate ? 'OPERATE' : 'STANDBY'}
              </button>
              {/* Fan mode — shown when the PowerGenius is configured (Settings →
                  PowerGenius). The dot shows the direct-connection state; the
                  selector is disabled until connected (hover it for the error). */}
              {selAmp?.type === 'pgxl' && (pg.host || pg.connected) && (
                <label className="flex items-center gap-1.5 text-xs" title={pg.connected ? t('flxp.pgConnected') : (pg.last_error || t('flxp.pgOffline'))}>
                  <span className={cn('size-1.5 rounded-full', pg.connected ? 'bg-success shadow-[0_0_6px_rgba(16,185,129,0.8)]' : 'bg-danger')} />
                  <span className="text-muted-foreground">{t('flxp.fan')}</span>
                  <select
                    disabled={!pg.connected}
                    value={(pg.fan_mode || 'CONTEST').toUpperCase()}
                    onChange={(e) => { const v = e.target.value; setPg((s) => ({ ...s, fan_mode: v })); PGXLSetFanMode(v).catch(() => {}); }}
                    className="h-8 rounded-md border border-warning-border bg-card px-2 text-xs font-semibold text-warning outline-none focus:border-warning disabled:opacity-40"
                  >
                    <option value="STANDARD">{t('flxp.fanStandard')}</option>
                    <option value="CONTEST">{t('flxp.fanContest')}</option>
                    <option value="BROADCAST">{t('flxp.fanBroadcast')}</option>
                  </select>
                </label>
              )}
              <div className="flex-1" />
              {st.amp_fault && st.amp_fault !== 'NONE' && (
                <span className="px-2 py-1 rounded bg-danger-muted text-danger-muted-foreground text-xs font-bold">{t('flxp.fault')}: {st.amp_fault}</span>
              )}
            </div>
            {/* Amplifier meters (FWD / ID / TEMP) — moved here from the Meters card
                and shown compact so they sit with the amp controls. */}
            {(() => {
              // Prefer the radio's VITA amp meters when they are flowing (local or
              // over a VPN) — fast, and the same values SmartSDR shows (incl. the
              // right temperature sensor). Fall back to the amp's OWN link only when
              // the radio isn't streaming them (a remote link without VITA).
              const meters = st.meters || [];
              const dbmToW = (d: number) => Math.pow(10, (d - 30) / 10);
              const amp = meters.filter((m) => (m.src || '').toUpperCase().includes('AMP')
                && !/^(RL|DRV)$/i.test((m.name || '').trim()));
              if (off || amp.length === 0) {
                const P = pg as any;
                if (!P.connected) return null;
                // Direct-link fallback: a 500 ms poll samples the SSB envelope at
                // random points, so peak-hold it (live=txing drops it the instant
                // PTT releases). The LDMOS pair can pull well past 25 A → 50 A scale.
                const txing = typeof st.transmitting === 'boolean' ? st.transmitting : /TRANSMIT/i.test(P.state || '');
                const w = txing ? Math.round(Number(P.fwd_w) || 0) : 0;
                const idA = txing ? Number(P.id) || 0 : 0;
                const temp = Number(P.temperature) || 0;
                return (
                  <div className="grid grid-cols-2 sm:grid-cols-3 gap-2 mt-2 pt-2 border-t border-border/50">
                    <MeterBar label={t('ampw.pwr')} value={peakHold('pgw', w, 2500, txing)} unit="W" lo={0} hi={2000} accent="#dc2626" />
                    <MeterBar label={t('ampw.id')} value={peakHold('pgid', idA, 2500, txing)} unit="A" lo={0} hi={50} accent="#16a34a" />
                    <MeterBar label={t('ampw.temp')} value={temp} unit="°C" lo={0} hi={80} accent="#ea580c" />
                  </div>
                );
              }
              // Power comes from the radio's meter stream and nothing else. The
              // amplifier also reports a "peakfwd", and using it was a mistake
              // twice over: it is a latched maximum that is never reset, and it
              // survives in the last-known status after the amp disconnects — so
              // it claimed 1350 W from an old transmission while the radio was
              // putting out 10.
              return (
                <div className="grid grid-cols-2 sm:grid-cols-3 gap-2 mt-2 pt-2 border-t border-border/50">
                  {amp.map((m) => {
                    if (/fwd|pwr/i.test(m.name || '') && /dbm/i.test(m.unit || '')) {
                      return <MeterBar key={m.id} label={m.name || `AMP ${m.id}`} value={peakHold(`amp${m.id}`, dbmToW(m.value))} unit="W" lo={0} hi={2000} accent="#dc2626" />;
                    }
                    const acc = /temp|degc|degf/i.test(`${m.unit}${m.name}`) ? '#ea580c' : /volt/i.test(m.unit || '') ? '#2563eb' : '#16a34a';
                    // Drain current (ID): the PGXL reports a full-scale far too small
                    // for this meter, so it pinned the bar. Give it a fixed 50 A scale
                    // (the LDMOS pair can pull ~45-50 A near full output), overriding an
                    // unusable reported range but keeping a sane one.
                    let lo = m.lo, hi = m.hi;
                    if (/amp/i.test(m.unit || '') || /^ID$|current/i.test(m.name || '')) {
                      lo = 0;
                      hi = m.hi >= 50 ? m.hi : 50;
                    }
                    return <MeterBar key={m.id} label={m.name || `AMP ${m.id}`} value={peakHold(`amp${m.id}`, m.value)} unit={m.unit} lo={lo} hi={hi} accent={acc} />;
                  })}
                </div>
              );
            })()}
          </Card>
        )}

        {/* Tuner Genius XL — 4O3A ATU, its own card when enabled (Settings →
            Tuner Genius). Same card shown in Station Control. */}
        {tgEnabled && <TunerCard status={tg} t={t} />}

      </div>
    </div>
  );
}
