import { useEffect, useMemo, useState } from 'react';
import {
  ArrowDown, ArrowUp, ArrowLeft, ArrowRight, Copy, Plus, Star, StarOff, Trash2,
  ChevronDown, ChevronRight,
  User, Database, Radio, Cog, Server, Award, Antenna as AntennaIcon,
  Compass, Wifi, Construction, UploadCloud,
} from 'lucide-react';
import {
  GetLookupSettings, SaveLookupSettings, ClearLookupCache, TestLookupProvider,
  GetListsSettings, SaveListsSettings,
  GetCATSettings, SaveCATSettings,
  ListProfiles, GetActiveProfile, SaveProfile, DeleteProfile, ActivateProfile, DuplicateProfile,
  GetRotatorSettings, SaveRotatorSettings, TestRotator, RotatorPark, RotatorStop,
  ListClusterServers, SaveClusterServer, DeleteClusterServer,
  GetClusterAutoConnect, SetClusterAutoConnect,
  ConnectClusterServer, DisconnectClusterServer,
  ConnectAllClusters, DisconnectAllClusters, GetClusterStatus,
  GetBackupSettings, SaveBackupSettings, RunBackupNow, PickBackupFolder,
  GetDatabaseSettings, PickOpenDatabase, PickSaveDatabase, OpenDatabase, MoveDatabase, ResetDatabaseToDefault, QuitApp,
  GetQSLDefaults, SaveQSLDefaults,
  GetExternalServices, SaveExternalServices, TestQRZUpload, TestClublogUpload,
  TestLoTWUpload, ListTQSLStationLocations,
  ComputeStationInfo,
} from '../../wailsjs/go/main/App';
import type { profile as profileModels } from '../../wailsjs/go/models';
import type { LookupSettingsForm, StationSettingsForm, ListsSettingsForm, ModePresetForm } from '@/types';
import type { main as mainModels, cluster as clusterModels } from '../../wailsjs/go/models';
import { EventsOn } from '../../wailsjs/runtime/runtime';

import {
  Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter, DialogDescription,
} from '@/components/ui/dialog';
import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
import { Label } from '@/components/ui/label';
import { Textarea } from '@/components/ui/textarea';
import { Checkbox } from '@/components/ui/checkbox';
import {
  Select, SelectTrigger, SelectValue, SelectContent, SelectItem,
} from '@/components/ui/select';
import { cn } from '@/lib/utils';
import { OperatingPanel } from '@/components/OperatingPanel';
import { UDPIntegrationsPanel } from '@/components/UDPIntegrationsPanel';

type LookupSettings = LookupSettingsForm;
type StationSettings = StationSettingsForm;
type ListsSettings = ListsSettingsForm;
type ModePreset = ModePresetForm;
type CATSettings = Omit<mainModels.CATSettings, 'convertValues'>;
type RotatorSettings = Omit<mainModels.RotatorSettings, 'convertValues'>;
type ClusterServer = Omit<clusterModels.ServerConfig, 'convertValues'>;
type ClusterServerStatus = Omit<clusterModels.ServerStatus, 'convertValues'>;
type Profile = Omit<profileModels.Profile, 'convertValues'>;

// Catalog of all standard ADIF bands, in natural frequency order. The user
// picks a subset on the right; everything else in the UI (entry strip,
// band-slot grid, band-map switcher) iterates that subset.
const BAND_CATALOG = [
  '2190m','630m','560m','160m','80m','60m','40m','30m','20m','17m','15m','12m','10m',
  '8m','6m','5m','4m','2m','1.25m','70cm','33cm','23cm','13cm','9cm','6cm','3cm','1.25cm',
  '6mm','4mm','2.5mm','2mm','1mm',
];

// Catalog of common ADIF modes with sensible RST defaults. When the user
// picks one on the right, the RSTs are pre-filled but stay editable.
const MODE_CATALOG: Array<{ name: string; sent: string; rcvd: string }> = [
  { name: 'SSB',          sent: '59',  rcvd: '59'  },
  { name: 'CW',           sent: '599', rcvd: '599' },
  { name: 'AM',           sent: '59',  rcvd: '59'  },
  { name: 'FM',           sent: '59',  rcvd: '59'  },
  { name: 'DIGITALVOICE', sent: '59',  rcvd: '59'  },
  { name: 'FT8',          sent: '-10', rcvd: '-10' },
  { name: 'FT4',          sent: '-10', rcvd: '-10' },
  { name: 'JS8',          sent: '-10', rcvd: '-10' },
  { name: 'MSK144',       sent: '+00', rcvd: '+00' },
  { name: 'JT65',         sent: '-15', rcvd: '-15' },
  { name: 'JT9',          sent: '-15', rcvd: '-15' },
  { name: 'Q65',          sent: '-15', rcvd: '-15' },
  { name: 'FST4',         sent: '-15', rcvd: '-15' },
  { name: 'FST4W',        sent: '-15', rcvd: '-15' },
  { name: 'WSPR',         sent: '-20', rcvd: '-20' },
  { name: 'RTTY',         sent: '599', rcvd: '599' },
  { name: 'PSK31',        sent: '599', rcvd: '599' },
  { name: 'PSK63',        sent: '599', rcvd: '599' },
  { name: 'PSK125',       sent: '599', rcvd: '599' },
  { name: 'OLIVIA',       sent: '599', rcvd: '599' },
  { name: 'CONTESTI',     sent: '599', rcvd: '599' },
  { name: 'MFSK',         sent: '599', rcvd: '599' },
  { name: 'THROB',        sent: '599', rcvd: '599' },
  { name: 'HELL',         sent: '599', rcvd: '599' },
  { name: 'PACKET',       sent: '599', rcvd: '599' },
  { name: 'PACTOR',       sent: '599', rcvd: '599' },
  { name: 'VARA',         sent: '599', rcvd: '599' },
  { name: 'VARA HF',      sent: '599', rcvd: '599' },
  { name: 'ARDOP',        sent: '599', rcvd: '599' },
  { name: 'ATV',          sent: '59',  rcvd: '59'  },
  { name: 'SSTV',         sent: '59',  rcvd: '59'  },
  { name: 'C4FM',         sent: '59',  rcvd: '59'  },
  { name: 'DSTAR',        sent: '59',  rcvd: '59'  },
  { name: 'DMR',          sent: '59',  rcvd: '59'  },
  { name: 'FUSION',       sent: '59',  rcvd: '59'  },
];

const emptyProfile = (): Profile => ({
  id: 0,
  name: '',
  callsign: '', operator: '', owner_callsign: '',
  my_grid: '', my_country: '',
  my_state: '', my_cnty: '',
  my_street: '', my_city: '', my_postal_code: '',
  my_sota_ref: '', my_pota_ref: '',
  my_rig: '', my_antenna: '',
  tx_pwr: undefined,
  is_active: false,
  sort_order: 0,
  created_at: '' as any,
  updated_at: '' as any,
});

interface Props {
  initialSection?: string;
  onClose: () => void;
  onSaved: () => void;
}

// Pretty little card showing what OpsLog will stamp on each QSO based on
// the callsign + grid in the Station Information form. Debounces the
// backend resolver so we don't fire on every keystroke; refreshes when
// inputs change. Empty card when no callsign yet.
/* ====== Tree definition ======
   Section IDs are stable strings — adding new ones means adding a panel below.
   `disabled: true` greys them out and shows the "coming soon" placeholder. */
type SectionId =
  | 'station'
  | 'profiles'
  | 'operating'
  | 'confirmations'
  | 'external-services'
  | 'udp'
  | 'lookup'
  | 'lists-bands'
  | 'lists-modes'
  | 'cluster'
  | 'backup'
  | 'database'
  | 'awards'
  | 'cat'
  | 'rotator'
  | 'antenna'
  | 'audio';

type TreeNode =
  | { kind: 'group'; label: string; icon?: any; defaultOpen?: boolean; children: TreeNode[] }
  | { kind: 'item';  label: string; id: SectionId; disabled?: boolean };

const TREE: TreeNode[] = [
  {
    kind: 'group', label: 'User Configuration', icon: User, defaultOpen: true, children: [
      { kind: 'item', label: 'Station Information', id: 'station' },
      { kind: 'item', label: 'Profiles', id: 'profiles' },
      { kind: 'item', label: 'Operating conditions', id: 'operating' },
      { kind: 'item', label: 'Confirmations', id: 'confirmations' },
      { kind: 'item', label: 'External services', id: 'external-services' },
    ],
  },
  {
    kind: 'group', label: 'Software Configuration', icon: Cog, defaultOpen: true, children: [
      { kind: 'item', label: 'Callsign Lookup', id: 'lookup' },
      { kind: 'group', label: 'Lists', icon: Database, defaultOpen: true, children: [
        { kind: 'item', label: 'Bands',      id: 'lists-bands' },
        { kind: 'item', label: 'Modes & default RST', id: 'lists-modes' },
      ]},
      { kind: 'item', label: 'DX Cluster',     id: 'cluster' },
      { kind: 'item', label: 'UDP integrations (WSJT-X, JTDX, MSHV…)', id: 'udp' },
      { kind: 'item', label: 'Database backup', id: 'backup' },
      { kind: 'item', label: 'Database location', id: 'database' },
      { kind: 'item', label: 'Awards',          id: 'awards',   disabled: true },
    ],
  },
  {
    kind: 'group', label: 'Hardware Configuration', icon: Server, defaultOpen: true, children: [
      { kind: 'item', label: 'CAT interface (OmniRig)',         id: 'cat' },
      { kind: 'item', label: 'Rotator (PstRotator)',            id: 'rotator' },
      { kind: 'item', label: 'Antenna (Ultrabeam)',             id: 'antenna', disabled: true },
      { kind: 'item', label: 'Audio devices',                   id: 'audio',   disabled: true },
    ],
  },
];

// Map section id → friendly name (used in breadcrumb / placeholders).
const SECTION_LABELS: Partial<Record<SectionId, string>> = {
  station: 'Station Information',
  profiles: 'Profiles',
  operating: 'Operating conditions',
  confirmations: 'Confirmations',
  'external-services': 'External services',
  lookup: 'Callsign Lookup',
  'lists-bands': 'Bands',
  'lists-modes': 'Modes & default RST',
  cluster: 'DX Cluster',
  backup: 'Database backup',
  database: 'Database location',
  udp: 'UDP integrations',
  awards: 'Awards',
  cat: 'CAT interface',
  rotator: 'Rotator',
  antenna: 'Antenna',
  audio: 'Audio devices',
};

// ===== Tree component =====

interface TreeProps {
  selected: SectionId;
  onSelect: (id: SectionId) => void;
}

function Tree({ selected, onSelect }: TreeProps) {
  return (
    <nav className="text-sm">
      {TREE.map((node, i) => (
        <TreeNodeView key={i} node={node} depth={0} selected={selected} onSelect={onSelect} />
      ))}
    </nav>
  );
}

function TreeNodeView({
  node, depth, selected, onSelect,
}: { node: TreeNode; depth: number; selected: SectionId; onSelect: (id: SectionId) => void }) {
  if (node.kind === 'item') {
    const isActive = selected === node.id;
    return (
      <button
        onClick={() => { if (!node.disabled) onSelect(node.id); }}
        disabled={node.disabled}
        className={cn(
          'w-full text-left px-2 py-1.5 rounded-md text-[12.5px] transition-colors flex items-center',
          isActive ? 'bg-accent text-accent-foreground font-semibold' : 'hover:bg-muted/60',
          node.disabled && 'opacity-50 cursor-not-allowed italic',
        )}
        style={{ paddingLeft: 8 + depth * 14 }}
      >
        <span className="truncate">{node.label}</span>
        {node.disabled && (
          <Construction className="ml-auto size-3 shrink-0 opacity-60" />
        )}
      </button>
    );
  }
  // group
  const [open, setOpen] = useState(node.defaultOpen ?? false);
  const Icon = node.icon;
  return (
    <div>
      <button
        onClick={() => setOpen((v) => !v)}
        className="w-full text-left px-2 py-1.5 rounded-md text-[12px] uppercase tracking-wider text-muted-foreground hover:text-foreground flex items-center gap-1.5 font-semibold"
        style={{ paddingLeft: 8 + depth * 14 }}
      >
        {open ? <ChevronDown className="size-3" /> : <ChevronRight className="size-3" />}
        {Icon && <Icon className="size-3.5 opacity-70" />}
        <span>{node.label}</span>
      </button>
      {open && (
        <div>
          {node.children.map((c, i) => (
            <TreeNodeView key={i} node={c} depth={depth + 1} selected={selected} onSelect={onSelect} />
          ))}
        </div>
      )}
    </div>
  );
}

// ===== Section content panels =====

function SectionHeader({ title, hint }: { title: string; hint?: string }) {
  return (
    <header className="mb-4">
      <h3 className="text-base font-semibold text-foreground">{title}</h3>
      {hint && <p className="text-xs text-muted-foreground mt-0.5">{hint}</p>}
    </header>
  );
}

function ComingSoon({ id, icon: Icon }: { id: SectionId; icon?: any }) {
  const label = SECTION_LABELS[id] ?? id;
  const IconCmp = Icon ?? Construction;
  return (
    <div className="flex flex-col items-center justify-center text-center text-muted-foreground gap-2 py-12">
      <IconCmp className="size-10 opacity-40" />
      <div className="text-base font-semibold text-foreground/70">{label}</div>
      <div className="text-sm">Module coming soon.</div>
    </div>
  );
}

export function SettingsModal({ onClose, onSaved, initialSection }: Props) {
  const [selected, setSelected] = useState<SectionId>((initialSection as SectionId) || 'station');
  const [loading, setLoading] = useState(true);
  const [saving, setSaving] = useState(false);
  const [clearing, setClearing] = useState(false);
  const [msg, setMsg] = useState('');
  const [err, setErr] = useState('');

  const [lookup, setLookup] = useState<LookupSettings>({
    qrz_user: '', qrz_password: '',
    hamqth_user: '', hamqth_password: '',
    primary: '', failsafe: '',
    download_images: false,
    cache_ttl_days: 30,
  });
  // Per-provider Test state — keeps the success/error feedback adjacent
  // to the button. Cleared on the next test run for that provider.
  type TestResult = { ok: boolean; msg: string };
  const [lookupTest, setLookupTest] = useState<Record<string, TestResult | undefined>>({});
  const [lookupTesting, setLookupTesting] = useState<Record<string, boolean>>({});
  // The Station Information panel now edits the full active profile
  // (not a flat 6-field StationSettings). Profile selection happens in
  // the Profiles panel; any edit here saves back to whichever profile
  // is currently active.
  const [activeProfile, setActiveProfile] = useState<Profile | null>(null);
  const updateActive = (patch: Partial<Profile>) =>
    setActiveProfile((p) => (p ? { ...p, ...patch } : p));
  const [lists, setLists] = useState<ListsSettings>({ bands: [], modes: [], rst_phone: [], rst_cw: [], rst_digital: [] });
  // RST report lists edited as free text (one/space-separated values).
  const [rstText, setRstText] = useState({ phone: '', cw: '', digital: '' });
  // Custom band drafts (catalog covers ADIF spec but the user may have
  // exotic or experimental bands not listed).
  const [bandDraft, setBandDraft] = useState('');
  const [modeDraft, setModeDraft] = useState('');
  const [catCfg, setCatCfg] = useState<CATSettings>({
    enabled: false, backend: 'omnirig', omnirig_rig: 1, poll_ms: 250, delay_ms: 0,
    digital_default: 'FT8',
  });
  const [rotator, setRotator] = useState<RotatorSettings>({
    enabled: false, host: '127.0.0.1', port: 12000, has_elevation: false,
  });
  const [rotatorTesting, setRotatorTesting] = useState(false);
  const [rotatorTest, setRotatorTest] = useState<{ ok: boolean; msg: string } | null>(null);

  type QSLDefaults = {
    qsl_sent: string; qsl_rcvd: string;
    lotw_sent: string; lotw_rcvd: string;
    eqsl_sent: string; eqsl_rcvd: string;
    clublog_status: string; hrdlog_status: string; qrzcom_status: string;
  };
  const [qslDefaults, setQslDefaults] = useState<QSLDefaults>({
    qsl_sent: '', qsl_rcvd: '',
    lotw_sent: '', lotw_rcvd: '',
    eqsl_sent: '', eqsl_rcvd: '',
    clublog_status: '', hrdlog_status: '', qrzcom_status: '',
  });

  // External services (logbook upload). One block per service; only QRZ is
  // wired today. upload_mode is 'immediate' | 'delayed' (per-service).
  type ExtServiceCfg = {
    api_key: string; email: string; username: string; password: string; callsign: string;
    force_station_callsign: string;
    tqsl_path: string; station_location: string; key_password: string;
    upload_flag: string; write_log: boolean;
    auto_upload: boolean; upload_mode: string;
  };
  type ExternalServices = { qrz: ExtServiceCfg; clublog: ExtServiceCfg; lotw: ExtServiceCfg };
  const emptyExtCfg = (): ExtServiceCfg => ({
    api_key: '', email: '', username: '', password: '', callsign: '',
    force_station_callsign: '', tqsl_path: '', station_location: '', key_password: '',
    upload_flag: 'R', write_log: false,
    auto_upload: false, upload_mode: 'immediate',
  });
  const [extSvc, setExtSvc] = useState<ExternalServices>({
    qrz: emptyExtCfg(), clublog: emptyExtCfg(), lotw: emptyExtCfg(),
  });
  const [qrzTest, setQrzTest] = useState<{ ok: boolean; msg: string } | null>(null);
  const [qrzTesting, setQrzTesting] = useState(false);
  const [clublogTest, setClublogTest] = useState<{ ok: boolean; msg: string } | null>(null);
  const [clublogTesting, setClublogTesting] = useState(false);
  const [lotwTest, setLotwTest] = useState<{ ok: boolean; msg: string } | null>(null);
  const [lotwTesting, setLotwTesting] = useState(false);
  const [stationLocations, setStationLocations] = useState<string[]>([]);
  // Active tab in the External Services panel — lifted here because
  // PANELS[selected]() is called as a function, so panels can't hold hooks.
  const [extSvcTab, setExtSvcTab] = useState<'qrz' | 'clublog' | 'hrdlog' | 'eqsl' | 'hamqth' | 'lotw'>('qrz');

  const [backupCfg, setBackupCfg] = useState<mainModels.BackupSettings>({
    enabled: false, folder: '', rotation: 5, zip: false,
    last_backup_at: '', default_folder: '',
  } as any);
  const [backupRunning, setBackupRunning] = useState(false);
  const [backupResult, setBackupResult] = useState<{ ok: boolean; msg: string } | null>(null);

  const [dbSettings, setDbSettings] = useState<{ path: string; default_path: string; is_custom: boolean }>({ path: '', default_path: '', is_custom: false });
  const [dbMsg, setDbMsg] = useState('');

  const [clusterServers, setClusterServers] = useState<ClusterServer[]>([]);
  const [clusterAutoConnect, setClusterAutoConnectState] = useState(false);
  const [clusterStatuses, setClusterStatuses] = useState<ClusterServerStatus[]>([]);
  const [editingServer, setEditingServer] = useState<ClusterServer | null>(null);

  async function reloadClusterServers() {
    try {
      const [list, ac, st] = await Promise.all([
        ListClusterServers(),
        GetClusterAutoConnect(),
        GetClusterStatus(),
      ]);
      setClusterServers((list ?? []) as ClusterServer[]);
      setClusterAutoConnectState(ac);
      setClusterStatuses((st ?? []) as ClusterServerStatus[]);
    } catch (e: any) { setErr(String(e?.message ?? e)); }
  }

  // Live cluster status updates while Preferences is open — the user can
  // click Connect/Disconnect inside the modal and see the pills change
  // without saving + reopening.
  useEffect(() => {
    const unsub = EventsOn('cluster:state', async (st: any) => {
      setClusterStatuses((st ?? []) as ClusterServerStatus[]);
      try {
        const list = await ListClusterServers();
        setClusterServers((list ?? []) as ClusterServer[]);
      } catch {}
    });
    return () => { unsub?.(); };
  }, []);
  const [profiles, setProfiles] = useState<Profile[]>([]);
  // State for ProfilesPanel — lifted here because PANELS[selected]() calls
  // the panel as a plain function, not as a JSX element, so any useState
  // inside the panel function would violate the Rules of Hooks.
  const [profileSelectedId, setProfileSelectedId] = useState<number>(0);
  const [profileNameDraft, setProfileNameDraft] = useState<string>('');

  async function reloadProfiles() {
    try {
      const list = await ListProfiles();
      setProfiles(list);
      // Refresh the active-profile editor in case activation changed.
      const ap = await GetActiveProfile();
      setActiveProfile(ap as Profile);
    } catch (e: any) {
      setErr(String(e?.message ?? e));
    }
  }

  // Keep the ProfilesPanel selector in sync with the loaded list. If the
  // currently-selected profile is gone (post-delete) or none is selected
  // yet, default to the active one.
  useEffect(() => {
    if (!profiles.length) return;
    const stillThere = profiles.some((p) => (p.id as number) === profileSelectedId);
    if (!stillThere) {
      const next = profiles.find((p) => p.is_active) ?? profiles[0];
      setProfileSelectedId(next.id as number);
      setProfileNameDraft(next.name);
    }
  }, [profiles, profileSelectedId]);

  useEffect(() => {
    (async () => {
      try {
        const [l, ls, c, ap, r, b, qd, es] = await Promise.all([
          GetLookupSettings(), GetListsSettings(), GetCATSettings(), GetActiveProfile(),
          GetRotatorSettings(), GetBackupSettings(), GetQSLDefaults(), GetExternalServices(),
        ]);
        setLookup(l);
        setActiveProfile(ap as Profile);
        setLists(ls);
        setRstText({
          phone: ((ls as any).rst_phone ?? []).join(' '),
          cw: ((ls as any).rst_cw ?? []).join(' '),
          digital: ((ls as any).rst_digital ?? []).join(' '),
        });
        await reloadProfiles();
        await reloadClusterServers();
        setCatCfg(c);
        setRotator(r);
        setBackupCfg(b as any);
        setQslDefaults(qd as any);
        setExtSvc(es as any);
        try { setDbSettings(await GetDatabaseSettings() as any); } catch {}
        try {
          const locs: any = await ListTQSLStationLocations();
          setStationLocations((locs ?? []).map((l: any) => l.name).filter(Boolean));
        } catch { /* TQSL not installed — leave the dropdown empty */ }
      } catch (e: any) {
        setErr(String(e?.message ?? e));
      } finally {
        setLoading(false);
      }
    })();
  }, []);

  // Auto-fill the active profile's MY_* DXCC metadata from the station
  // callsign (country, DXCC#, CQ/ITU zones) and the grid (lat/lon). These
  // are derived values, so they always recompute when the callsign or grid
  // changes — the user can still edit a field, it just re-populates when the
  // source changes. Debounced so we don't hammer cty.dat while typing.
  useEffect(() => {
    const call = (activeProfile?.callsign ?? '').trim();
    if (!call) return;
    const grid = (activeProfile?.my_grid ?? '').trim();
    const t = window.setTimeout(async () => {
      try {
        const i: any = await ComputeStationInfo(call, grid);
        setActiveProfile((p) => {
          if (!p) return p;
          const patch: any = {};
          if (i.country) patch.my_country = i.country;
          if (i.dxcc) patch.my_dxcc = i.dxcc;
          if (i.cqz) patch.my_cqz = i.cqz;
          if (i.ituz) patch.my_ituz = i.ituz;
          if (i.lat) patch.my_lat = i.lat;
          if (i.lon) patch.my_lon = i.lon;
          // Only re-render when a value actually changed (prevents loops).
          const changed = Object.keys(patch).some((k) => (p as any)[k] !== patch[k]);
          return changed ? { ...p, ...patch } : p;
        });
      } catch { /* offline / unknown prefix — leave fields as-is */ }
    }, 250);
    return () => window.clearTimeout(t);
  }, [activeProfile?.callsign, activeProfile?.my_grid]);

  // ── Band selection helpers (dual-list shuttle) ──────────────────────────
  function addBand(tag: string) {
    const b = tag.trim().toLowerCase();
    if (!b) return;
    setLists((l) => {
      if ((l.bands ?? []).includes(b)) return l;
      return { ...l, bands: [...(l.bands ?? []), b] };
    });
  }
  function removeBand(i: number) {
    setLists((l) => {
      const next = [...(l.bands ?? [])];
      next.splice(i, 1);
      return { ...l, bands: next };
    });
  }
  function moveBand(i: number, dir: -1 | 1) {
    setLists((l) => {
      const next = [...(l.bands ?? [])];
      const j = i + dir;
      if (j < 0 || j >= next.length) return l;
      [next[i], next[j]] = [next[j], next[i]];
      return { ...l, bands: next };
    });
  }

  // ── Mode helpers ────────────────────────────────────────────────────────
  function addMode() {
    setLists((l) => ({
      ...l,
      modes: [...(l.modes ?? []), { name: '', default_rst_sent: '59', default_rst_rcvd: '59' }],
    }));
  }
  function addModeFromCatalog(m: { name: string; sent: string; rcvd: string }) {
    setLists((l) => {
      if ((l.modes ?? []).some((x) => (x.name ?? '').toUpperCase() === m.name)) return l;
      return {
        ...l,
        modes: [...(l.modes ?? []), { name: m.name, default_rst_sent: m.sent, default_rst_rcvd: m.rcvd }],
      };
    });
  }
  function addCustomMode(name: string) {
    const n = name.trim().toUpperCase();
    if (!n) return;
    setLists((l) => {
      if ((l.modes ?? []).some((x) => (x.name ?? '').toUpperCase() === n)) return l;
      return {
        ...l,
        modes: [...(l.modes ?? []), { name: n, default_rst_sent: '59', default_rst_rcvd: '59' }],
      };
    });
  }
  function removeMode(i: number) {
    setLists((l) => {
      const next = [...(l.modes ?? [])];
      next.splice(i, 1);
      return { ...l, modes: next };
    });
  }
  function moveMode(i: number, dir: -1 | 1) {
    setLists((l) => {
      const next = [...(l.modes ?? [])];
      const j = i + dir;
      if (j < 0 || j >= next.length) return l;
      [next[i], next[j]] = [next[j], next[i]];
      return { ...l, modes: next };
    });
  }
  function updateMode(i: number, patch: Partial<ModePreset>) {
    setLists((l) => {
      const next = [...(l.modes ?? [])];
      next[i] = { ...next[i], ...patch } as ModePreset;
      return { ...l, modes: next };
    });
  }

  async function save() {
    setSaving(true); setErr(''); setMsg('');
    try {
      // Bands: dedup, lowercase, trim. Order = user's drag order.
      const seen = new Set<string>();
      const bands: string[] = [];
      for (const raw of lists.bands ?? []) {
        const b = (raw ?? '').trim().toLowerCase();
        if (b && !seen.has(b)) { seen.add(b); bands.push(b); }
      }
      const modes = (lists.modes ?? [])
        .map((m) => ({
          name: (m.name ?? '').trim().toUpperCase(),
          default_rst_sent: (m.default_rst_sent ?? '').trim(),
          default_rst_rcvd: (m.default_rst_rcvd ?? '').trim(),
        }))
        .filter((m) => m.name !== '');
      const splitList = (s: string) => s.split(/[\s,]+/).map((x) => x.trim()).filter(Boolean);
      await SaveListsSettings({
        bands, modes,
        rst_phone: splitList(rstText.phone),
        rst_cw: splitList(rstText.cw),
        rst_digital: splitList(rstText.digital),
      } as any);

      if (activeProfile) {
        await SaveProfile({
          ...activeProfile,
          callsign:     (activeProfile.callsign     ?? '').trim().toUpperCase(),
          operator:     (activeProfile.operator     ?? '').trim().toUpperCase(),
          my_grid:      (activeProfile.my_grid      ?? '').trim().toUpperCase(),
          my_sota_ref:  (activeProfile.my_sota_ref  ?? '').trim().toUpperCase(),
          my_pota_ref:  (activeProfile.my_pota_ref  ?? '').trim().toUpperCase(),
        } as any);
      }
      await SaveLookupSettings(lookup as any);
      await SaveCATSettings(catCfg as any);
      await SaveRotatorSettings(rotator as any);
      await SaveBackupSettings(backupCfg as any);
      await SaveQSLDefaults(qslDefaults as any);
      await SaveExternalServices(extSvc as any);
      await SetClusterAutoConnect(clusterAutoConnect);

      setMsg('Settings saved.');
      onSaved();
      setTimeout(onClose, 500);
    } catch (e: any) {
      setErr(String(e?.message ?? e));
    } finally {
      setSaving(false);
    }
  }

  async function clearCache() {
    setClearing(true); setErr(''); setMsg('');
    try {
      await ClearLookupCache();
      setMsg('Cache cleared.');
    } catch (e: any) {
      setErr(String(e?.message ?? e));
    } finally {
      setClearing(false);
    }
  }

  async function testProvider(provider: 'qrz' | 'hamqth') {
    setLookupTesting((s) => ({ ...s, [provider]: true }));
    setLookupTest((s) => ({ ...s, [provider]: undefined }));
    const user = provider === 'qrz' ? lookup.qrz_user : lookup.hamqth_user;
    const pwd  = provider === 'qrz' ? lookup.qrz_password : lookup.hamqth_password;
    try {
      const r = await TestLookupProvider(provider, '', user ?? '', pwd ?? '');
      setLookupTest((s) => ({ ...s, [provider]: { ok: true, msg: `OK — ${r.callsign} (${r.name || r.country || 'no name'})` } }));
    } catch (e: any) {
      setLookupTest((s) => ({ ...s, [provider]: { ok: false, msg: String(e?.message ?? e) } }));
    } finally {
      setLookupTesting((s) => ({ ...s, [provider]: false }));
    }
  }

  const breadcrumb = useMemo(() => SECTION_LABELS[selected] ?? selected, [selected]);

  // === Section content renderers ===

  function StationPanel() {
    if (!activeProfile) {
      return <div className="text-muted-foreground text-sm">Loading profile…</div>;
    }
    const p = activeProfile;
    return (
      <>
        <SectionHeader
          title="Station Information"
          hint={`Editing the active profile: ${p.name}. Switch profiles in the Profiles section to edit a different one.`}
        />
        <div className="grid grid-cols-2 gap-3 max-w-2xl">
          <div className="space-y-1">
            <Label>Station callsign</Label>
            <Input className="font-mono uppercase" value={p.callsign ?? ''} onChange={(e) => updateActive({ callsign: e.target.value })} placeholder="F4XYZ" />
            <div className="text-[10px] text-muted-foreground">What's transmitted (ADIF STATION_CALLSIGN).</div>
          </div>
          <div className="space-y-1">
            <Label>Operator callsign</Label>
            <Input className="font-mono uppercase" value={p.operator ?? ''} onChange={(e) => updateActive({ operator: e.target.value })} placeholder="F4XYZ" />
            <div className="text-[10px] text-muted-foreground">Who's at the radio (ADIF OPERATOR).</div>
          </div>
          <div className="space-y-1 col-span-2">
            <Label>Owner callsign</Label>
            <Input className="font-mono uppercase max-w-xs" value={p.owner_callsign ?? ''} onChange={(e) => updateActive({ owner_callsign: e.target.value })} placeholder="(leave blank if same as station)" />
            <div className="text-[10px] text-muted-foreground">Legal station owner — only differs at club stations or remote setups (ADIF STATION_OWNER).</div>
          </div>
          <div className="col-span-2 text-[10px] text-muted-foreground uppercase tracking-wider mt-1">
            Auto-filled from the callsign — editable (stamped as MY_* on each QSO)
          </div>
          <div className="space-y-1">
            <Label>My grid</Label>
            <Input className="font-mono uppercase" value={p.my_grid ?? ''} onChange={(e) => updateActive({ my_grid: e.target.value })} placeholder="JN18BU" />
          </div>
          <div className="space-y-1">
            <Label>My country</Label>
            <Input value={p.my_country ?? ''} onChange={(e) => updateActive({ my_country: e.target.value })} placeholder="France" />
          </div>
          <div className="grid grid-cols-3 gap-2 col-span-2">
            <div className="space-y-1">
              <Label>DXCC #</Label>
              <Input type="number" className="font-mono" value={(p as any).my_dxcc ?? ''}
                onChange={(e) => updateActive({ my_dxcc: e.target.value === '' ? undefined : (parseInt(e.target.value, 10) || undefined) } as any)} placeholder="—" />
            </div>
            <div className="space-y-1">
              <Label>CQ zone</Label>
              <Input type="number" className="font-mono" value={(p as any).my_cqz ?? ''}
                onChange={(e) => updateActive({ my_cqz: e.target.value === '' ? undefined : (parseInt(e.target.value, 10) || undefined) } as any)} placeholder="—" />
            </div>
            <div className="space-y-1">
              <Label>ITU zone</Label>
              <Input type="number" className="font-mono" value={(p as any).my_ituz ?? ''}
                onChange={(e) => updateActive({ my_ituz: e.target.value === '' ? undefined : (parseInt(e.target.value, 10) || undefined) } as any)} placeholder="—" />
            </div>
            <div className="space-y-1">
              <Label>Latitude</Label>
              <Input type="number" step="0.001" className="font-mono" value={(p as any).my_lat ?? ''}
                onChange={(e) => updateActive({ my_lat: e.target.value === '' ? undefined : (parseFloat(e.target.value) || undefined) } as any)} placeholder="—" />
            </div>
            <div className="space-y-1">
              <Label>Longitude</Label>
              <Input type="number" step="0.001" className="font-mono" value={(p as any).my_lon ?? ''}
                onChange={(e) => updateActive({ my_lon: e.target.value === '' ? undefined : (parseFloat(e.target.value) || undefined) } as any)} placeholder="—" />
            </div>
          </div>
          <div className="space-y-1">
            <Label>State / pref</Label>
            <Input value={p.my_state ?? ''} onChange={(e) => updateActive({ my_state: e.target.value })} />
          </div>
          <div className="space-y-1">
            <Label>County</Label>
            <Input value={p.my_cnty ?? ''} onChange={(e) => updateActive({ my_cnty: e.target.value })} />
          </div>
          <div className="space-y-1 col-span-2">
            <Label>Street address</Label>
            <Input value={p.my_street ?? ''} onChange={(e) => updateActive({ my_street: e.target.value })} />
          </div>
          <div className="space-y-1">
            <Label>Postal code</Label>
            <Input value={p.my_postal_code ?? ''} onChange={(e) => updateActive({ my_postal_code: e.target.value })} />
          </div>
          <div className="space-y-1">
            <Label>City</Label>
            <Input value={p.my_city ?? ''} onChange={(e) => updateActive({ my_city: e.target.value })} />
          </div>
          <div className="space-y-1">
            <Label>SOTA ref</Label>
            <Input className="font-mono uppercase" value={p.my_sota_ref ?? ''} onChange={(e) => updateActive({ my_sota_ref: e.target.value })} placeholder="F/AB-001" />
          </div>
          <div className="space-y-1">
            <Label>POTA ref</Label>
            <Input className="font-mono uppercase" value={p.my_pota_ref ?? ''} onChange={(e) => updateActive({ my_pota_ref: e.target.value })} placeholder="FF-1234" />
          </div>
        </div>
      </>
    );
  }

  // Profile actions — kept at the SettingsModal level so the ProfilesPanel
  // renderer can stay hooks-free (the PANELS map calls it as a plain
  // function, not as a JSX component).
  const activeProfileObj = profiles.find((p) => p.is_active) ?? profiles[0];
  const currentProfile = profiles.find((p) => (p.id as number) === profileSelectedId);

  async function profileActivate() {
    if (!currentProfile) return;
    try { await ActivateProfile(currentProfile.id as number); await reloadProfiles(); onSaved(); }
    catch (e: any) { setErr(String(e?.message ?? e)); }
  }
  async function profileRemove() {
    if (!currentProfile) return;
    if (!confirm(`Delete profile "${currentProfile.name}"? All its settings will be lost.`)) return;
    try { await DeleteProfile(currentProfile.id as number); await reloadProfiles(); }
    catch (e: any) { setErr(String(e?.message ?? e)); }
  }
  async function profileDuplicate() {
    if (!currentProfile) return;
    const name = prompt(`Name for the new profile (copy of "${currentProfile.name}"):`, `${currentProfile.name} Copy`);
    if (!name?.trim()) return;
    try {
      const dup = await DuplicateProfile(currentProfile.id as number, name.trim());
      await reloadProfiles();
      setProfileSelectedId(dup.id as number);
      setProfileNameDraft(dup.name);
    } catch (e: any) { setErr(String(e?.message ?? e)); }
  }
  async function profileCreateBlank() {
    const name = prompt('Name for the new profile:', 'New profile');
    if (!name?.trim()) return;
    try {
      const blank = emptyProfile();
      blank.name = name.trim();
      const saved = await SaveProfile(blank as any);
      await reloadProfiles();
      setProfileSelectedId(saved.id as number);
      setProfileNameDraft(saved.name);
    } catch (e: any) { setErr(String(e?.message ?? e)); }
  }
  async function profileRenameCurrent() {
    if (!currentProfile || profileNameDraft.trim() === currentProfile.name) return;
    try {
      await SaveProfile({ ...currentProfile, name: profileNameDraft.trim() } as any);
      await reloadProfiles();
    } catch (e: any) { setErr(String(e?.message ?? e)); }
  }

  function ProfilesPanel() {
    const current = currentProfile;
    const active = activeProfileObj;
    return (
      <>
        <SectionHeader
          title="Profiles"
          hint="Switch between operating identities (home / portable / SOTA / contest). Pick a profile here, then edit its fields in the other sections (Station Information, etc.) — changes are saved against the selected profile."
        />
        <div className="space-y-4">
          <div className="grid grid-cols-[140px_1fr] items-center gap-x-3 gap-y-3">
            <Label>Configuration ID</Label>
            <Select
              value={String(profileSelectedId)}
              onValueChange={(v) => {
                const id = parseInt(v, 10);
                setProfileSelectedId(id);
                setProfileNameDraft(profiles.find((p) => (p.id as number) === id)?.name ?? '');
              }}
            >
              <SelectTrigger><SelectValue /></SelectTrigger>
              <SelectContent>
                {profiles.map((p) => (
                  <SelectItem key={p.id as number} value={String(p.id)}>
                    {p.name}{p.callsign ? ` — ${p.callsign}` : ''}{p.is_active ? ' (active)' : ''}
                  </SelectItem>
                ))}
              </SelectContent>
            </Select>

            <Label>Description</Label>
            <div className="flex items-center gap-2">
              <Input
                value={profileNameDraft}
                onChange={(e) => setProfileNameDraft(e.target.value)}
                onBlur={profileRenameCurrent}
                placeholder="Profile name"
                disabled={!current}
              />
              {current?.is_active && (
                <span className="inline-flex items-center px-2 py-0.5 rounded text-[10px] font-bold tracking-wider bg-emerald-100 text-emerald-800 border border-emerald-300">
                  ACTIVE
                </span>
              )}
            </div>
          </div>

          <div className="flex items-center gap-2 pt-2 border-t border-border">
            <Button variant="outline" size="sm" onClick={profileCreateBlank} title="Create a new empty profile">
              <Plus className="size-3.5 mr-1" /> New
            </Button>
            <Button variant="outline" size="sm" onClick={profileDuplicate} disabled={!current} title="Clone the selected profile (keeps all its fields)">
              <Copy className="size-3.5 mr-1" /> Duplicate
            </Button>
            <Button
              variant="outline" size="sm"
              onClick={profileActivate}
              disabled={!current || current.is_active}
              title="Activate the selected profile — new QSOs will use its MY_* fields"
            >
              <Star className="size-3.5 mr-1" /> Set active
            </Button>
            <Button
              variant="outline" size="sm"
              onClick={profileRemove}
              disabled={!current || profiles.length <= 1}
              className="text-destructive hover:text-destructive ml-auto"
              title={profiles.length <= 1 ? 'Cannot delete the last profile' : 'Delete the selected profile'}
            >
              <Trash2 className="size-3.5 mr-1" /> Delete
            </Button>
          </div>

          {current && !current.is_active && (
            <div className="text-xs text-muted-foreground bg-muted/30 border border-border rounded-md p-2.5">
              You're viewing <strong>{current.name}</strong>. The active profile is <strong>{active?.name}</strong> — its values are stamped on new QSOs. Click <em>Set active</em> to switch.
            </div>
          )}
        </div>
      </>
    );
  }

  function LookupPanel() {
    // Per-row provider editor — kept inline because it's only used twice
    // and needs closure access to the parent state.
    const row = (
      key: 'qrz' | 'hamqth', label: string, userField: 'qrz_user' | 'hamqth_user',
      pwdField: 'qrz_password' | 'hamqth_password',
    ) => {
      const test = lookupTest[key];
      const testing = lookupTesting[key];
      const hasCreds = !!(lookup[userField] && lookup[pwdField]);
      return (
        <tr className="border-t border-border align-middle">
          <td className="px-3 py-2 font-semibold whitespace-nowrap">{label}</td>
          <td className="px-2 py-2 text-center">
            <input
              type="radio"
              name="lookup-primary"
              checked={lookup.primary === key}
              onChange={() => setLookup((s) => ({ ...s, primary: key, failsafe: s.failsafe === key ? '' : s.failsafe }))}
              disabled={!hasCreds}
            />
          </td>
          <td className="px-2 py-2 text-center">
            <input
              type="radio"
              name="lookup-failsafe"
              checked={lookup.failsafe === key}
              onChange={() => setLookup((s) => ({ ...s, failsafe: key, primary: s.primary === key ? '' : s.primary }))}
              disabled={!hasCreds || lookup.primary === key}
            />
          </td>
          <td className="px-2 py-2">
            <Input
              className="h-8"
              value={lookup[userField] ?? ''}
              onChange={(e) => setLookup((s) => ({ ...s, [userField]: e.target.value }))}
              placeholder="User"
              autoComplete="off"
            />
          </td>
          <td className="px-2 py-2">
            <Input
              className="h-8"
              type="password"
              value={lookup[pwdField] ?? ''}
              onChange={(e) => setLookup((s) => ({ ...s, [pwdField]: e.target.value }))}
              placeholder="Password"
              autoComplete="off"
            />
          </td>
          <td className="px-2 py-2 whitespace-nowrap">
            <Button
              variant="outline" size="sm"
              onClick={() => testProvider(key)}
              disabled={!hasCreds || testing}
              title="Run a sample lookup against the active profile's callsign to verify credentials"
            >
              {testing ? 'Testing…' : 'Test'}
            </Button>
          </td>
          <td className={cn('px-2 py-2 text-xs', test?.ok ? 'text-emerald-700' : 'text-destructive')}>
            {test?.msg}
          </td>
        </tr>
      );
    };
    return (
      <>
        <SectionHeader
          title="Callsign Lookup"
          hint="Pick a Primary provider and an optional Failsafe (queried only when Primary returns no data). Click Test to verify credentials without saving."
        />
        <div className="rounded-md border border-border overflow-hidden">
          <table className="w-full text-sm">
            <thead className="bg-muted/40 text-[10px] uppercase tracking-wider text-muted-foreground">
              <tr>
                <th className="text-left px-3 py-2">Provider</th>
                <th className="px-2 py-2 w-20">Primary</th>
                <th className="px-2 py-2 w-20">Failsafe</th>
                <th className="text-left px-2 py-2">User</th>
                <th className="text-left px-2 py-2">Password</th>
                <th className="px-2 py-2 w-20"></th>
                <th className="text-left px-2 py-2">Result</th>
              </tr>
            </thead>
            <tbody>
              {row('qrz',    'QRZ.com', 'qrz_user',    'qrz_password')}
              {row('hamqth', 'HamQTH',  'hamqth_user', 'hamqth_password')}
            </tbody>
          </table>
        </div>
        <p className="text-xs text-muted-foreground mt-2">
          Failsafe is consulted only when the Primary returns no match or errors. Set both to none (uncheck) during contests to skip the network entirely.
        </p>

        <div className="mt-6 pt-4 border-t border-border">
          <h3 className="text-sm font-semibold mb-2">Display</h3>
          <label className="flex items-start gap-2 text-sm cursor-pointer">
            <Checkbox
              checked={lookup.download_images}
              onCheckedChange={(c) => setLookup((s) => ({ ...s, download_images: !!c }))}
              className="mt-0.5"
            />
            <span>
              Show QRZ profile pictures
              <span className="block text-xs text-muted-foreground mt-0.5">
                Display the photo from QRZ.com next to the worked-before matrix.
                May noticeably slow lookups during busy contest days; turn off if you operate fast.
              </span>
            </span>
          </label>
        </div>

        <div className="mt-6 pt-4 border-t border-border">
          <h3 className="text-sm font-semibold mb-2">Cache</h3>
          <p className="text-xs text-muted-foreground mb-3">
            Successful lookups are cached locally so the same callsign isn't fetched twice. TTL controls how long before a fresh query is made.
          </p>
          <div className="flex gap-3 items-end">
            <div className="space-y-1 w-40">
              <Label>TTL (days)</Label>
              <Input
                type="number" min={1} max={3650}
                value={lookup.cache_ttl_days}
                onChange={(e) => setLookup((s) => ({ ...s, cache_ttl_days: parseInt(e.target.value) || 30 }))}
              />
            </div>
            <Button variant="outline" onClick={clearCache} disabled={clearing}>
              {clearing ? 'Clearing…' : 'Clear cache now'}
            </Button>
          </div>
        </div>
      </>
    );
  }

  function BandsPanel() {
    const selected = lists.bands ?? [];
    const selectedSet = new Set(selected.map((b) => (b ?? '').toLowerCase()));
    const available = BAND_CATALOG.filter((b) => !selectedSet.has(b.toLowerCase()));
    return (
      <>
        <SectionHeader
          title="Bands"
          hint="Pick the bands you actually use. The entry strip, the band-slot grid and the band-map switcher only show what's on the right. Order on the right = display order."
        />
        <div className="grid grid-cols-[1fr_auto_1fr] gap-3 max-w-3xl">
          {/* Left: available catalog */}
          <div className="rounded-md border border-border overflow-hidden">
            <div className="px-3 py-1.5 bg-muted text-[10px] uppercase tracking-wider font-semibold text-muted-foreground">
              Available
            </div>
            <div className="max-h-[320px] overflow-y-auto divide-y divide-border">
              {available.length === 0 ? (
                <div className="px-3 py-2 text-xs text-muted-foreground italic">All catalog bands selected.</div>
              ) : (
                available.map((b) => (
                  <button
                    key={b}
                    type="button"
                    onDoubleClick={() => addBand(b)}
                    onClick={() => addBand(b)}
                    className="w-full text-left px-3 py-1.5 text-sm font-mono hover:bg-accent/40 transition-colors"
                  >
                    {b}
                  </button>
                ))
              )}
            </div>
            <div className="flex gap-1 px-2 py-1.5 border-t border-border bg-muted/40">
              <Input
                value={bandDraft}
                onChange={(e) => setBandDraft(e.target.value)}
                onKeyDown={(e) => {
                  if (e.key === 'Enter') {
                    addBand(bandDraft);
                    setBandDraft('');
                  }
                }}
                placeholder="Custom band (e.g. 4m)"
                className="font-mono h-7 text-xs"
              />
              <Button
                size="sm"
                variant="outline"
                className="h-7"
                onClick={() => { addBand(bandDraft); setBandDraft(''); }}
                disabled={!bandDraft.trim()}
              >
                <Plus className="size-3" />
              </Button>
            </div>
          </div>

          {/* Center: shuttle hint */}
          <div className="flex flex-col items-center justify-center gap-2 text-muted-foreground">
            <ArrowRight className="size-4" />
            <ArrowLeft className="size-4" />
          </div>

          {/* Right: selected */}
          <div className="rounded-md border border-border overflow-hidden">
            <div className="px-3 py-1.5 bg-muted text-[10px] uppercase tracking-wider font-semibold text-muted-foreground flex items-center justify-between">
              <span>Selected ({selected.length})</span>
            </div>
            <div className="max-h-[360px] overflow-y-auto divide-y divide-border">
              {selected.length === 0 ? (
                <div className="px-3 py-2 text-xs text-muted-foreground italic">
                  No band selected — pick from the left.
                </div>
              ) : (
                selected.map((b, i) => (
                  <div key={`${b}-${i}`} className="grid grid-cols-[auto_1fr_auto] items-center gap-1 px-2 py-1">
                    <div className="flex gap-0.5">
                      <Button size="icon" variant="ghost" className="size-6" onClick={() => moveBand(i, -1)} disabled={i === 0}>
                        <ArrowUp className="size-3" />
                      </Button>
                      <Button size="icon" variant="ghost" className="size-6" onClick={() => moveBand(i, 1)} disabled={i === selected.length - 1}>
                        <ArrowDown className="size-3" />
                      </Button>
                    </div>
                    <span className="font-mono text-sm">{b}</span>
                    <Button size="icon" variant="ghost" className="size-6 text-destructive hover:bg-destructive/10" onClick={() => removeBand(i)}>
                      <Trash2 className="size-3" />
                    </Button>
                  </div>
                ))
              )}
            </div>
          </div>
        </div>
      </>
    );
  }

  function ModesPanel() {
    const selected = lists.modes ?? [];
    const selectedSet = new Set(selected.map((m) => (m.name ?? '').toUpperCase()));
    const available = MODE_CATALOG.filter((m) => !selectedSet.has(m.name));
    return (
      <>
        <SectionHeader
          title="Modes & default RST"
          hint="Pick the modes you actually use on the right. Anywhere the UI shows a mode picker, it iterates the right column. When you select a mode in the entry form, RST sent/rcvd auto-fill with the defaults below (unless you've typed something)."
        />
        <div className="grid grid-cols-[1fr_auto_1.5fr] gap-3 max-w-4xl">
          {/* Left: available catalog */}
          <div className="rounded-md border border-border overflow-hidden">
            <div className="px-3 py-1.5 bg-muted text-[10px] uppercase tracking-wider font-semibold text-muted-foreground">
              Available
            </div>
            <div className="max-h-[360px] overflow-y-auto divide-y divide-border">
              {available.length === 0 ? (
                <div className="px-3 py-2 text-xs text-muted-foreground italic">All catalog modes selected.</div>
              ) : (
                available.map((m) => (
                  <button
                    key={m.name}
                    type="button"
                    onDoubleClick={() => addModeFromCatalog(m)}
                    onClick={() => addModeFromCatalog(m)}
                    className="w-full text-left px-3 py-1.5 text-sm font-mono hover:bg-accent/40 transition-colors flex items-center justify-between gap-2"
                    title={`Default RST: ${m.sent} / ${m.rcvd}`}
                  >
                    <span>{m.name}</span>
                    <span className="text-[10px] text-muted-foreground">{m.sent}/{m.rcvd}</span>
                  </button>
                ))
              )}
            </div>
            <div className="flex gap-1 px-2 py-1.5 border-t border-border bg-muted/40">
              <Input
                value={modeDraft}
                onChange={(e) => setModeDraft(e.target.value)}
                onKeyDown={(e) => {
                  if (e.key === 'Enter') {
                    addCustomMode(modeDraft);
                    setModeDraft('');
                  }
                }}
                placeholder="Custom mode"
                className="font-mono uppercase h-7 text-xs"
              />
              <Button
                size="sm"
                variant="outline"
                className="h-7"
                onClick={() => { addCustomMode(modeDraft); setModeDraft(''); }}
                disabled={!modeDraft.trim()}
              >
                <Plus className="size-3" />
              </Button>
            </div>
          </div>

          {/* Center: shuttle hint */}
          <div className="flex flex-col items-center justify-center gap-2 text-muted-foreground">
            <ArrowRight className="size-4" />
            <ArrowLeft className="size-4" />
          </div>

          {/* Right: selected with editable RST */}
          <div className="rounded-md border border-border overflow-hidden">
            <div className="grid grid-cols-[auto_1fr_72px_72px_auto] gap-2 px-3 py-1.5 bg-muted text-[10px] uppercase tracking-wider font-semibold text-muted-foreground">
              <span className="w-12">Order</span>
              <span>Mode</span>
              <span>RST snt</span>
              <span>RST rcv</span>
              <span className="w-6"></span>
            </div>
            <div className="max-h-[360px] overflow-y-auto divide-y divide-border">
              {selected.length === 0 ? (
                <div className="px-3 py-2 text-xs text-muted-foreground italic">
                  No mode selected — pick from the left.
                </div>
              ) : (
                selected.map((m, i) => (
                  <div key={i} className="grid grid-cols-[auto_1fr_72px_72px_auto] gap-2 px-3 py-1 items-center">
                    <div className="flex gap-0.5 w-12">
                      <Button size="icon" variant="ghost" className="size-6" onClick={() => moveMode(i, -1)} disabled={i === 0}>
                        <ArrowUp className="size-3" />
                      </Button>
                      <Button size="icon" variant="ghost" className="size-6" onClick={() => moveMode(i, 1)} disabled={i === selected.length - 1}>
                        <ArrowDown className="size-3" />
                      </Button>
                    </div>
                    <Input className="font-mono uppercase h-7" value={m.name} onChange={(e) => updateMode(i, { name: e.target.value })} placeholder="SSB" />
                    <Input className="font-mono h-7" value={m.default_rst_sent ?? ''} onChange={(e) => updateMode(i, { default_rst_sent: e.target.value })} placeholder="59" />
                    <Input className="font-mono h-7" value={m.default_rst_rcvd ?? ''} onChange={(e) => updateMode(i, { default_rst_rcvd: e.target.value })} placeholder="59" />
                    <Button size="icon" variant="ghost" className="size-6 text-destructive hover:bg-destructive/10" onClick={() => removeMode(i)}>
                      <Trash2 className="size-3" />
                    </Button>
                  </div>
                ))
              )}
            </div>
            <div className="px-3 py-1.5 border-t border-border bg-muted/40">
              <Button variant="ghost" size="sm" onClick={addMode} className="h-6 text-xs">
                <Plus className="size-3" /> Add blank row
              </Button>
            </div>
          </div>
        </div>

        {/* RST report lists — the dropdown choices in the entry form. */}
        <div className="mt-6 max-w-4xl">
          <div className="text-sm font-semibold mb-1">RST report lists</div>
          <div className="text-[11px] text-muted-foreground mb-2">
            The choices offered in the entry form's RST dropdowns, per mode family. One value per line (or space-separated). The first one is the top of the list.
          </div>
          <div className="grid grid-cols-3 gap-3">
            <div className="space-y-1">
              <Label className="text-xs">Phone (SSB/AM/FM)</Label>
              <Textarea rows={8} className="font-mono text-xs" value={rstText.phone} onChange={(e) => setRstText((s) => ({ ...s, phone: e.target.value }))} />
            </div>
            <div className="space-y-1">
              <Label className="text-xs">CW / RTTY / PSK</Label>
              <Textarea rows={8} className="font-mono text-xs" value={rstText.cw} onChange={(e) => setRstText((s) => ({ ...s, cw: e.target.value }))} />
            </div>
            <div className="space-y-1">
              <Label className="text-xs">Digital (FT8/FT4/JT…) — dB</Label>
              <Textarea rows={8} className="font-mono text-xs" value={rstText.digital} onChange={(e) => setRstText((s) => ({ ...s, digital: e.target.value }))} />
            </div>
          </div>
        </div>
      </>
    );
  }

  function CATPanel() {
    return (
      <>
        <SectionHeader
          title="CAT interface (OmniRig)"
          hint="Reads the rig's current frequency / band / mode and pushes them into the entry strip in real time. Requires OmniRig (free) installed and configured separately for your rig — OpsLog just talks to it."
        />
        <div className="space-y-4 max-w-lg">
          <label className="flex items-center gap-2 text-sm cursor-pointer">
            <Checkbox checked={catCfg.enabled} onCheckedChange={(c) => setCatCfg((s) => ({ ...s, enabled: !!c }))} />
            Enable CAT
          </label>

          <div className="grid grid-cols-2 gap-3">
            <div className="space-y-1">
              <Label>Backend</Label>
              <Select value={catCfg.backend} onValueChange={(v) => setCatCfg((s) => ({ ...s, backend: v }))}>
                <SelectTrigger><SelectValue /></SelectTrigger>
                <SelectContent>
                  <SelectItem value="omnirig">OmniRig (Windows COM)</SelectItem>
                  <SelectItem value="flex" disabled>Flex SmartSDR (coming soon)</SelectItem>
                </SelectContent>
              </Select>
            </div>
            <div className="space-y-1">
              <Label>OmniRig rig slot</Label>
              <Select value={String(catCfg.omnirig_rig)} onValueChange={(v) => setCatCfg((s) => ({ ...s, omnirig_rig: parseInt(v) as 1 | 2 }))}>
                <SelectTrigger><SelectValue /></SelectTrigger>
                <SelectContent>
                  <SelectItem value="1">Rig 1</SelectItem>
                  <SelectItem value="2">Rig 2</SelectItem>
                </SelectContent>
              </Select>
            </div>
            <div className="space-y-1">
              <Label>Poll interval (ms)</Label>
              <Input
                type="number" min={50} max={2000} step={50}
                value={catCfg.poll_ms}
                onChange={(e) => setCatCfg((s) => ({ ...s, poll_ms: parseInt(e.target.value) || 250 }))}
              />
            </div>
            <div className="space-y-1">
              <Label>CAT delay (ms)</Label>
              <Input
                type="number" min={0} max={500} step={10}
                value={catCfg.delay_ms}
                onChange={(e) => setCatCfg((s) => ({ ...s, delay_ms: parseInt(e.target.value) || 0 }))}
              />
            </div>
            <div className="space-y-1 col-span-2">
              <Label>Default digital mode (when rig reports DIG)</Label>
              <Select
                value={catCfg.digital_default || 'FT8'}
                onValueChange={(v) => setCatCfg((s) => ({ ...s, digital_default: v }))}
              >
                <SelectTrigger><SelectValue /></SelectTrigger>
                <SelectContent>
                  {['FT8','FT4','RTTY','PSK31','MFSK','JS8','JT65','JT9','OLIVIA','DIGITALVOICE','DATA'].map(m => (
                    <SelectItem key={m} value={m}>{m}</SelectItem>
                  ))}
                </SelectContent>
              </Select>
            </div>
          </div>
          <p className="text-xs text-muted-foreground">
            Configure your rig (COM port, baud rate, model) in OmniRig's own settings GUI first.
            OpsLog will read whichever Rig slot you select here. Set <strong>CAT delay</strong>
            {' '}above 0 if your rig drops commands sent back-to-back (some older Kenwood/Yaesu).
            OmniRig only reports generic "DIG" for digital modes — <strong>Default digital mode</strong>
            {' '}is the specific mode OpsLog will surface (and log).
          </p>
        </div>
      </>
    );
  }

  async function testRotator() {
    setRotatorTesting(true);
    setRotatorTest(null);
    try {
      await TestRotator(rotator as any);
      setRotatorTest({ ok: true, msg: 'Packet sent — antenna should swing to 0° (north). If it didn\'t, check PstRotator host/port and that PstRotator\'s UDP listener is enabled.' });
    } catch (e: any) {
      setRotatorTest({ ok: false, msg: String(e?.message ?? e) });
    } finally {
      setRotatorTesting(false);
    }
  }

  function RotatorPanel() {
    return (
      <>
        <SectionHeader
          title="Rotator (PstRotator)"
          hint="OpsLog sends UDP commands to PstRotator. Enable PstRotator's UDP listener (Setup → Communication → UDP) before testing."
        />
        <div className="space-y-4 max-w-xl">
          <label className="flex items-center gap-2 text-sm cursor-pointer">
            <Checkbox checked={rotator.enabled} onCheckedChange={(c) => setRotator((s) => ({ ...s, enabled: !!c }))} />
            Enable PstRotator control
          </label>
          <div className="grid grid-cols-3 gap-3">
            <div className="space-y-1 col-span-2">
              <Label>Host</Label>
              <Input
                value={rotator.host ?? ''}
                onChange={(e) => setRotator((s) => ({ ...s, host: e.target.value }))}
                placeholder="127.0.0.1"
                className="font-mono"
              />
            </div>
            <div className="space-y-1">
              <Label>UDP port</Label>
              <Input
                type="number" min={1} max={65535}
                value={rotator.port}
                onChange={(e) => setRotator((s) => ({ ...s, port: parseInt(e.target.value) || 12000 }))}
                className="font-mono"
              />
            </div>
          </div>
          <label className="flex items-center gap-2 text-sm cursor-pointer">
            <Checkbox checked={rotator.has_elevation} onCheckedChange={(c) => setRotator((s) => ({ ...s, has_elevation: !!c }))} />
            This rotator supports elevation (VHF / satellite)
          </label>
          <div className="flex items-center gap-2 pt-2">
            <Button variant="outline" size="sm" onClick={testRotator} disabled={rotatorTesting}>
              {rotatorTesting ? 'Sending…' : 'Test (point to 0°)'}
            </Button>
            <Button variant="outline" size="sm" onClick={() => RotatorStop().catch((e) => setErr(String(e?.message ?? e)))} disabled={!rotator.enabled}>
              Stop
            </Button>
            <Button variant="outline" size="sm" onClick={() => RotatorPark().catch((e) => setErr(String(e?.message ?? e)))} disabled={!rotator.enabled}>
              Park
            </Button>
          </div>
          {rotatorTest && (
            <div className={cn(
              'text-xs rounded-md p-2.5 border',
              rotatorTest.ok
                ? 'bg-emerald-50 text-emerald-800 border-emerald-200'
                : 'bg-destructive/10 text-destructive border-destructive/30',
            )}>
              {rotatorTest.msg}
            </div>
          )}
          <p className="text-xs text-muted-foreground">
            From the main entry strip, click the bearing pill to rotate to the short-path azimuth.
            Shift+click for long-path, Ctrl+click to stop.
          </p>
        </div>
      </>
    );
  }

  function statusForServer(id: number): ClusterServerStatus | undefined {
    return clusterStatuses.find((s) => (s.server_id as number) === id);
  }

  async function clusterToggleEnabled(srv: ClusterServer, on: boolean) {
    try {
      await SaveClusterServer({ ...srv, enabled: on } as any);
      await reloadClusterServers();
    } catch (e: any) { setErr(String(e?.message ?? e)); }
  }
  async function clusterDeleteServer(srv: ClusterServer) {
    if (!confirm(`Delete cluster "${srv.name}"? Active session will be closed.`)) return;
    try { await DeleteClusterServer(srv.id as number); await reloadClusterServers(); }
    catch (e: any) { setErr(String(e?.message ?? e)); }
  }
  async function clusterMove(srv: ClusterServer, dir: -1 | 1) {
    const sorted = [...clusterServers].sort((a, b) => (a.sort_order ?? 0) - (b.sort_order ?? 0));
    const idx = sorted.findIndex((s) => s.id === srv.id);
    const j = idx + dir;
    if (idx < 0 || j < 0 || j >= sorted.length) return;
    const a = sorted[idx], b = sorted[j];
    try {
      await SaveClusterServer({ ...a, sort_order: b.sort_order } as any);
      await SaveClusterServer({ ...b, sort_order: a.sort_order } as any);
      await reloadClusterServers();
    } catch (e: any) { setErr(String(e?.message ?? e)); }
  }
  function clusterAddNew() {
    const next: ClusterServer = {
      id: 0, name: '', host: '', port: 7300,
      login_override: '', password: '', init_commands: '',
      enabled: true, sort_order: clusterServers.length,
    };
    setEditingServer(next);
  }

  function ClusterPanel() {
    const sorted = [...clusterServers].sort((a, b) => (a.sort_order ?? 0) - (b.sort_order ?? 0));
    return (
      <>
        <SectionHeader
          title="DX Cluster"
          hint="Connect to one or several DX cluster nodes (telnet). The first enabled server is the master — typed commands and init commands go through it."
        />
        <div className="space-y-4">
          <div className="rounded-md border border-border overflow-hidden">
            <table className="w-full text-sm">
              <thead className="bg-muted/40 text-[10px] uppercase tracking-wider text-muted-foreground">
                <tr>
                  <th className="px-3 py-2 w-10"></th>
                  <th className="text-left px-3 py-2">Name</th>
                  <th className="text-left px-3 py-2">Host:port</th>
                  <th className="text-left px-3 py-2 w-28">Status</th>
                  <th className="px-3 py-2 w-32">Actions</th>
                </tr>
              </thead>
              <tbody>
                {sorted.map((s, i) => {
                  const st = statusForServer(s.id as number);
                  const state = (st?.state ?? 'disconnected') as string;
                  const isMaster = i === sorted.findIndex((x) => x.enabled);
                  return (
                    <tr key={s.id as number} className="border-t border-border align-middle">
                      <td className="px-2 py-2 text-center">
                        <Checkbox
                          checked={s.enabled}
                          onCheckedChange={(c) => clusterToggleEnabled(s, !!c)}
                        />
                      </td>
                      <td className="px-3 py-2 font-medium">
                        {s.name}
                        {isMaster && s.enabled && (
                          <span className="ml-2 inline-flex items-center px-1.5 py-0 rounded text-[9px] font-bold tracking-wider bg-amber-100 text-amber-800 border border-amber-300">MASTER</span>
                        )}
                      </td>
                      <td className="px-3 py-2 font-mono text-xs">{s.host}:{s.port}</td>
                      <td className="px-3 py-2">
                        <span className={cn(
                          'inline-flex items-center px-1.5 py-0 rounded text-[9px] font-bold tracking-wider border',
                          state === 'connected' ? 'bg-emerald-100 text-emerald-800 border-emerald-300' :
                          state === 'connecting' || state === 'reconnecting' ? 'bg-amber-100 text-amber-800 border-amber-300' :
                          state === 'error' ? 'bg-rose-100 text-rose-800 border-rose-300' :
                          'bg-muted text-muted-foreground border-border',
                        )}>
                          {state.toUpperCase()}
                          {st?.retries ? ` #${st.retries}` : ''}
                        </span>
                      </td>
                      <td className="px-2 py-2">
                        <div className="flex items-center gap-0.5 justify-end">
                          <Button variant="ghost" size="icon" className="size-6" disabled={i === 0} onClick={() => clusterMove(s, -1)} title="Move up"><ArrowUp className="size-3" /></Button>
                          <Button variant="ghost" size="icon" className="size-6" disabled={i === sorted.length - 1} onClick={() => clusterMove(s, 1)} title="Move down"><ArrowDown className="size-3" /></Button>
                          <Button variant="ghost" size="icon" className="size-6" onClick={() => setEditingServer(s)} title="Edit"><Cog className="size-3.5" /></Button>
                          <Button variant="ghost" size="icon" className="size-6 text-destructive hover:text-destructive" onClick={() => clusterDeleteServer(s)} title="Delete"><Trash2 className="size-3.5" /></Button>
                        </div>
                      </td>
                    </tr>
                  );
                })}
                {sorted.length === 0 && (
                  <tr><td colSpan={5} className="px-3 py-4 text-center text-muted-foreground text-xs">No cluster nodes saved yet.</td></tr>
                )}
              </tbody>
            </table>
          </div>

          <div className="flex items-center gap-3 pt-2 border-t border-border">
            <Button variant="outline" size="sm" onClick={clusterAddNew}>
              <Plus className="size-3.5 mr-1" /> Add cluster
            </Button>
            <Button variant="outline" size="sm" onClick={async () => { await ConnectAllClusters().catch(() => {}); await reloadClusterServers(); }}>
              Connect all
            </Button>
            <Button variant="outline" size="sm" onClick={async () => { await DisconnectAllClusters().catch(() => {}); await reloadClusterServers(); }}>
              Disconnect all
            </Button>
            <label className="flex items-center gap-2 text-sm cursor-pointer ml-auto">
              <Checkbox checked={clusterAutoConnect} onCheckedChange={(c) => setClusterAutoConnectState(!!c)} />
              Auto-connect all enabled on app start
            </label>
          </div>
          <p className="text-xs text-muted-foreground">
            Free public nodes: <span className="font-mono">dxc.k0xm.net:7300</span>,{' '}
            <span className="font-mono">dx.maritimecontestclub.net:7300</span>,{' '}
            <span className="font-mono">w8avi.net:7300</span>.
          </p>
        </div>

        {editingServer && (
          <ClusterServerEditor
            value={editingServer}
            onCancel={() => setEditingServer(null)}
            onSave={async (srv) => {
              try {
                await SaveClusterServer(srv as any);
                await reloadClusterServers();
                setEditingServer(null);
              } catch (e: any) { setErr(String(e?.message ?? e)); }
            }}
          />
        )}
      </>
    );
  }

  function ConfirmationsPanel() {
    // ADIF status codes. FULL set for paper QSL/eQSL/Clublog/HRDLog,
    // SIMPLE Y/N set for LoTW (the only values the LoTW protocol returns).
    const FULL_OPTIONS = [
      { value: '_', label: '— leave blank —' },
      { value: 'Y', label: 'Y (yes)' },
      { value: 'N', label: 'N (no)' },
      { value: 'R', label: 'R (requested)' },
      { value: 'Q', label: 'Q (queued)' },
      { value: 'I', label: 'I (ignore)' },
    ];
    // LoTW / Clublog / HRDLog also use ADIF-style status codes — keep
    // R (requested) available so users can mark "queued for upload"
    // and filter on it later.

    // Renderer inlined as a constant — declaring this as a function
    // INSIDE ConfirmationsPanel would re-instantiate the component on
    // every render, which unmounts and re-mounts the Radix Select
    // (closing it the moment you click the trigger).
    const renderSelect = (
      key: keyof QSLDefaults,
      options: { value: string; label: string }[],
    ) => (
      <Select
        value={qslDefaults[key] || '_'}
        onValueChange={(v) => setQslDefaults((d) => ({ ...d, [key]: v === '_' ? '' : v }))}
      >
        <SelectTrigger className="h-8"><SelectValue /></SelectTrigger>
        <SelectContent>
          {options.map((o) => (
            <SelectItem key={o.value} value={o.value}>{o.label}</SelectItem>
          ))}
        </SelectContent>
      </Select>
    );

    return (
      <>
        <SectionHeader
          title="Confirmations"
          hint="Default QSL / eQSL / LoTW / upload status applied to every QSO you log — manually or via UDP auto-log from WSJT-X / JTDX / MSHV. Leave a field blank to keep the QSO column empty."
        />
        <div className="space-y-3 max-w-2xl">
          {/* Paper QSL */}
          <div className="grid grid-cols-[150px_1fr_1fr] gap-3 items-end">
            <Label className="text-sm font-medium pb-1.5">Paper QSL</Label>
            <div>
              <Label className="text-[10px] text-muted-foreground uppercase tracking-wider mb-1 block">Sent</Label>
              {renderSelect('qsl_sent', FULL_OPTIONS)}
            </div>
            <div>
              <Label className="text-[10px] text-muted-foreground uppercase tracking-wider mb-1 block">Rcvd</Label>
              {renderSelect('qsl_rcvd', FULL_OPTIONS)}
            </div>
          </div>
          {/* eQSL */}
          <div className="grid grid-cols-[150px_1fr_1fr] gap-3 items-end">
            <Label className="text-sm font-medium pb-1.5">eQSL</Label>
            <div>
              <Label className="text-[10px] text-muted-foreground uppercase tracking-wider mb-1 block">Sent</Label>
              {renderSelect('eqsl_sent', FULL_OPTIONS)}
            </div>
            <div>
              <Label className="text-[10px] text-muted-foreground uppercase tracking-wider mb-1 block">Rcvd</Label>
              {renderSelect('eqsl_rcvd', FULL_OPTIONS)}
            </div>
          </div>
          {/* LoTW */}
          <div className="grid grid-cols-[150px_1fr_1fr] gap-3 items-end">
            <Label className="text-sm font-medium pb-1.5">LoTW</Label>
            <div>
              <Label className="text-[10px] text-muted-foreground uppercase tracking-wider mb-1 block">Sent</Label>
              {renderSelect('lotw_sent', FULL_OPTIONS)}
            </div>
            <div>
              <Label className="text-[10px] text-muted-foreground uppercase tracking-wider mb-1 block">Rcvd</Label>
              {renderSelect('lotw_rcvd', FULL_OPTIONS)}
            </div>
          </div>

          <div className="border-t border-border/60 pt-3 space-y-3">
            <div className="text-[11px] text-muted-foreground">
              Upload status fields (Clublog / HRDLog / QRZ.com) are stamped on every QSO; "N" is typical when you want OpsLog to track which QSOs still need to be uploaded.
            </div>
            {/* Clublog */}
            <div className="grid grid-cols-[150px_1fr_1fr] gap-3 items-end">
              <Label className="text-sm font-medium pb-1.5">Clublog</Label>
              <div>
                <Label className="text-[10px] text-muted-foreground uppercase tracking-wider mb-1 block">Upload status</Label>
                {renderSelect('clublog_status', FULL_OPTIONS)}
              </div>
              <div />
            </div>
            {/* HRDLog */}
            <div className="grid grid-cols-[150px_1fr_1fr] gap-3 items-end">
              <Label className="text-sm font-medium pb-1.5">HRDLog</Label>
              <div>
                <Label className="text-[10px] text-muted-foreground uppercase tracking-wider mb-1 block">Upload status</Label>
                {renderSelect('hrdlog_status', FULL_OPTIONS)}
              </div>
              <div />
            </div>
            {/* QRZ.com */}
            <div className="grid grid-cols-[150px_1fr_1fr] gap-3 items-end">
              <Label className="text-sm font-medium pb-1.5">QRZ.com</Label>
              <div>
                <Label className="text-[10px] text-muted-foreground uppercase tracking-wider mb-1 block">Upload status</Label>
                {renderSelect('qrzcom_status', FULL_OPTIONS)}
              </div>
              <div />
            </div>
          </div>
        </div>
      </>
    );
  }

  function UDPIntegrationsPanelWrapper() {
    return (
      <>
        <SectionHeader
          title="UDP integrations"
          hint="Listen for QSO logs from WSJT-X / JTDX / MSHV, ADIF messages from JTAlert/GridTracker, or simple callsign packets from external tools. Outbound connections forward every QSO you log to a remote listener (Cloudlog UDP, N1MM, …)."
        />
        <UDPIntegrationsPanel onError={(m) => setErr(m)} />
      </>
    );
  }

  function OperatingPanelWrapper() {
    return (
      <>
        <SectionHeader
          title="Operating conditions"
          hint="Define your rigs and the antennas you use on each band. The entry strip will auto-fill MY_RIG and MY_ANTENNA based on the default antenna for the band you're operating on."
        />
        <OperatingPanel
          bands={lists.bands ?? []}
          onError={(m) => setErr(m)}
        />
      </>
    );
  }

  function BackupPanel() {
    const fmtLast = (iso: string) => {
      if (!iso) return 'never';
      const d = new Date(iso);
      if (isNaN(d.getTime())) return iso;
      const p = (n: number) => String(n).padStart(2, '0');
      return `${d.getUTCFullYear()}-${p(d.getUTCMonth() + 1)}-${p(d.getUTCDate())} ${p(d.getUTCHours())}:${p(d.getUTCMinutes())} UTC`;
    };
    const effectiveFolder = (backupCfg.folder || backupCfg.default_folder || '').replace(/\\/g, '/');
    async function backupNow() {
      setBackupRunning(true); setBackupResult(null);
      try {
        // Save current draft first so the backup runs with the values
        // the user just typed (folder, rotation, zip) — otherwise the
        // backend would use stale persisted config.
        await SaveBackupSettings(backupCfg as any);
        const path = await RunBackupNow();
        setBackupResult({ ok: true, msg: 'Backup written to ' + path });
        const refreshed = await GetBackupSettings();
        setBackupCfg(refreshed as any);
      } catch (e: any) {
        setBackupResult({ ok: false, msg: String(e?.message ?? e) });
      } finally { setBackupRunning(false); }
    }
    return (
      <>
        <SectionHeader
          title="Database backup"
          hint="OpsLog can copy the SQLite database to a folder of your choice when you close it, once per day. Rotation keeps the last N copies and deletes older ones."
        />
        <div className="space-y-4 max-w-xl">
          <label className="flex items-center gap-2 text-sm cursor-pointer">
            <Checkbox
              checked={!!backupCfg.enabled}
              onCheckedChange={(c) => setBackupCfg((b) => ({ ...b, enabled: !!c }))}
            />
            <span>Automatic backup when closing OpsLog (max once per day)</span>
          </label>

          <div className="space-y-1.5">
            <Label className="text-xs text-muted-foreground">Backup folder</Label>
            <div className="flex gap-2">
              <Input
                className="font-mono text-xs flex-1"
                placeholder={backupCfg.default_folder || 'leave empty for default'}
                value={backupCfg.folder ?? ''}
                onChange={(e) => setBackupCfg((b) => ({ ...b, folder: e.target.value }))}
              />
              <Button
                variant="outline"
                size="sm"
                onClick={async () => {
                  try {
                    const p = await PickBackupFolder();
                    if (p) setBackupCfg((b) => ({ ...b, folder: p }));
                  } catch (e: any) {
                    setBackupResult({ ok: false, msg: String(e?.message ?? e) });
                  }
                }}
              >
                Browse…
              </Button>
            </div>
            <div className="text-[10px] text-muted-foreground">
              {backupCfg.folder
                ? <>Effective folder: <span className="font-mono">{effectiveFolder}</span></>
                : <>If empty, OpsLog uses the default: <span className="font-mono">{(backupCfg.default_folder ?? '').replace(/\\/g, '/')}</span></>
              }
            </div>
          </div>

          <div className="flex items-end gap-3">
            <div className="space-y-1.5">
              <Label className="text-xs text-muted-foreground">Rotation (copies to keep)</Label>
              <Input
                type="number"
                min={1}
                max={365}
                className="w-24 font-mono text-xs"
                value={backupCfg.rotation || 5}
                onChange={(e) => {
                  const n = Number(e.target.value);
                  if (Number.isFinite(n) && n > 0) setBackupCfg((b) => ({ ...b, rotation: Math.floor(n) }));
                }}
              />
            </div>
            <label className="flex items-center gap-2 text-sm cursor-pointer pb-2">
              <Checkbox
                checked={!!backupCfg.zip}
                onCheckedChange={(c) => setBackupCfg((b) => ({ ...b, zip: !!c }))}
              />
              <span>ZIP backup (smaller file)</span>
            </label>
          </div>

          <div className="border-t border-border/60 pt-3 flex items-center gap-3">
            <Button size="sm" onClick={backupNow} disabled={backupRunning}>
              {backupRunning ? 'Backing up…' : 'Backup now'}
            </Button>
            <span className="text-xs text-muted-foreground">
              Last backup: <strong className="text-foreground">{fmtLast(backupCfg.last_backup_at)}</strong>
            </span>
          </div>

          {backupResult && (
            <div className={cn(
              'text-xs px-3 py-2 rounded-md border',
              backupResult.ok
                ? 'bg-emerald-50 border-emerald-300 text-emerald-800'
                : 'bg-rose-50 border-rose-300 text-rose-800',
            )}>
              {backupResult.msg}
            </div>
          )}
        </div>
      </>
    );
  }

  function ExternalServicesPanel() {
    const TABS: { k: typeof extSvcTab; label: string; ready?: boolean }[] = [
      { k: 'qrz',     label: 'QRZ.COM', ready: true },
      { k: 'clublog', label: 'CLUBLOG', ready: true },
      { k: 'hrdlog',  label: 'HRDLOG.NET' },
      { k: 'eqsl',    label: 'EQSL' },
      { k: 'hamqth',  label: 'HAMQTH' },
      { k: 'lotw',    label: 'LOTW', ready: true },
    ];
    const qrz = extSvc.qrz;
    const setQrz = (patch: Partial<ExtServiceCfg>) =>
      setExtSvc((s) => ({ ...s, qrz: { ...s.qrz, ...patch } }));
    const clublog = extSvc.clublog;
    const setClublog = (patch: Partial<ExtServiceCfg>) =>
      setExtSvc((s) => ({ ...s, clublog: { ...s.clublog, ...patch } }));

    async function testQrz() {
      setQrzTesting(true);
      setQrzTest(null);
      try {
        // Persist first so the backend test reads the key just typed.
        await SaveExternalServices(extSvc as any);
        const msg = await TestQRZUpload();
        setQrzTest({ ok: true, msg });
      } catch (e: any) {
        setQrzTest({ ok: false, msg: String(e?.message ?? e) });
      } finally {
        setQrzTesting(false);
      }
    }

    async function testClublog() {
      setClublogTesting(true);
      setClublogTest(null);
      try {
        await SaveExternalServices(extSvc as any);
        const msg = await TestClublogUpload();
        setClublogTest({ ok: true, msg });
      } catch (e: any) {
        setClublogTest({ ok: false, msg: String(e?.message ?? e) });
      } finally {
        setClublogTesting(false);
      }
    }

    const lotw = extSvc.lotw;
    const setLotw = (patch: Partial<ExtServiceCfg>) =>
      setExtSvc((s) => ({ ...s, lotw: { ...s.lotw, ...patch } }));

    async function refreshLocations() {
      try {
        const locs: any = await ListTQSLStationLocations();
        setStationLocations((locs ?? []).map((l: any) => l.name).filter(Boolean));
      } catch (e: any) {
        setLotwTest({ ok: false, msg: String(e?.message ?? e) });
      }
    }

    async function testLotw() {
      setLotwTesting(true);
      setLotwTest(null);
      try {
        await SaveExternalServices(extSvc as any);
        const msg = await TestLoTWUpload();
        setLotwTest({ ok: true, msg });
      } catch (e: any) {
        setLotwTest({ ok: false, msg: String(e?.message ?? e) });
      } finally {
        setLotwTesting(false);
      }
    }

    return (
      <>
        <SectionHeader
          title="External services"
          hint="Upload logged QSOs to online logbooks. Each service uploads automatically on a new QSO when enabled; timing is per-service (immediate, or a 1–2 min delay so a mis-logged QSO can still be fixed first)."
        />

        {/* Tab strip */}
        <div className="flex flex-wrap gap-1 border-b border-border mb-4">
          {TABS.map((t) => (
            <button
              key={t.k}
              type="button"
              onClick={() => setExtSvcTab(t.k)}
              className={cn(
                'px-3 py-1.5 text-xs font-semibold rounded-t-md border-x border-t -mb-px transition-colors',
                extSvcTab === t.k
                  ? 'bg-card border-border text-foreground'
                  : 'bg-muted/40 border-transparent text-muted-foreground hover:text-foreground',
              )}
            >
              {t.label}
              {!t.ready && <span className="ml-1 text-[9px] opacity-60">soon</span>}
            </button>
          ))}
        </div>

        {extSvcTab === 'qrz' ? (
          <div className="space-y-4 max-w-2xl">
            <div className="grid grid-cols-[170px_1fr] gap-3 items-center">
              <Label className="text-sm">API key</Label>
              <Input
                value={qrz.api_key}
                onChange={(e) => setQrz({ api_key: e.target.value })}
                placeholder="QRZ.com logbook API key (XXXX-XXXX-XXXX-XXXX)"
                className="font-mono text-xs"
              />
              <Label className="text-sm">Force station callsign</Label>
              <Input
                value={qrz.force_station_callsign}
                onChange={(e) => setQrz({ force_station_callsign: e.target.value.toUpperCase() })}
                placeholder="e.g. F4BPO — optional"
                className="font-mono text-xs"
              />
            </div>

            <div className="border-t border-border/60 pt-3 space-y-3">
              <label className="flex items-center gap-2 text-sm cursor-pointer">
                <Checkbox
                  checked={qrz.auto_upload}
                  onCheckedChange={(c) => setQrz({ auto_upload: !!c })}
                />
                Automatic upload on new QSO
              </label>

              <div className="grid grid-cols-[170px_1fr] gap-3 items-center">
                <Label className="text-sm">Upload timing</Label>
                <Select
                  value={qrz.upload_mode || 'immediate'}
                  onValueChange={(v) => setQrz({ upload_mode: v })}
                >
                  <SelectTrigger className="h-8 w-64"><SelectValue /></SelectTrigger>
                  <SelectContent>
                    <SelectItem value="immediate">Immediate</SelectItem>
                    <SelectItem value="delayed">Delayed (1–2 min, lets you fix mistakes)</SelectItem>
                    <SelectItem value="on_close">On app close (batch)</SelectItem>
                  </SelectContent>
                </Select>
              </div>

              <div className="flex items-center gap-3">
                <Button variant="outline" size="sm" onClick={testQrz} disabled={qrzTesting || !qrz.api_key}>
                  <UploadCloud className="size-3.5" /> {qrzTesting ? 'Testing…' : 'Test connection'}
                </Button>
                {qrzTest && (
                  <span className={cn('text-xs', qrzTest.ok ? 'text-emerald-700' : 'text-rose-700')}>
                    {qrzTest.msg}
                  </span>
                )}
              </div>
            </div>
          </div>
        ) : extSvcTab === 'clublog' ? (
          <div className="space-y-4 max-w-2xl">
            <div className="grid grid-cols-[170px_1fr] gap-3 items-center">
              <Label className="text-sm">Account email</Label>
              <Input
                type="email"
                value={clublog.email}
                onChange={(e) => setClublog({ email: e.target.value })}
                placeholder="your Club Log account email"
                className="text-xs"
              />
              <Label className="text-sm">Password</Label>
              <Input
                type="password"
                value={clublog.password}
                onChange={(e) => setClublog({ password: e.target.value })}
                placeholder="Club Log account password"
                className="text-xs"
              />
              <Label className="text-sm">Logbook callsign</Label>
              <Input
                value={clublog.callsign}
                onChange={(e) => setClublog({ callsign: e.target.value.toUpperCase() })}
                placeholder="defaults to the active profile's callsign"
                className="font-mono text-xs"
              />
            </div>

            <div className="border-t border-border/60 pt-3 space-y-3">
              <label className="flex items-center gap-2 text-sm cursor-pointer">
                <Checkbox
                  checked={clublog.auto_upload}
                  onCheckedChange={(c) => setClublog({ auto_upload: !!c })}
                />
                Automatic upload on new QSO
              </label>

              <div className="grid grid-cols-[170px_1fr] gap-3 items-center">
                <Label className="text-sm">Upload timing</Label>
                <Select
                  value={clublog.upload_mode || 'immediate'}
                  onValueChange={(v) => setClublog({ upload_mode: v })}
                >
                  <SelectTrigger className="h-8 w-64"><SelectValue /></SelectTrigger>
                  <SelectContent>
                    <SelectItem value="immediate">Immediate</SelectItem>
                    <SelectItem value="delayed">Delayed (1–2 min, lets you fix mistakes)</SelectItem>
                    <SelectItem value="on_close">On app close (batch)</SelectItem>
                  </SelectContent>
                </Select>
              </div>

              <div className="flex items-center gap-3">
                <Button variant="outline" size="sm" onClick={testClublog} disabled={clublogTesting}>
                  <UploadCloud className="size-3.5" /> {clublogTesting ? 'Testing…' : 'Test connection'}
                </Button>
                {clublogTest && (
                  <span className={cn('text-xs', clublogTest.ok ? 'text-emerald-700' : 'text-rose-700')}>
                    {clublogTest.msg}
                  </span>
                )}
              </div>
            </div>
          </div>
        ) : extSvcTab === 'lotw' ? (
          <div className="space-y-4 max-w-2xl">
            <div className="grid grid-cols-[170px_1fr] gap-3 items-center">
              <Label className="text-sm">LoTW user</Label>
              <Input
                value={lotw.username}
                onChange={(e) => setLotw({ username: e.target.value })}
                placeholder="LoTW website login (for downloading confirmations)"
                className="font-mono text-xs"
              />
              <Label className="text-sm">LoTW password</Label>
              <Input
                type="password"
                value={lotw.password}
                onChange={(e) => setLotw({ password: e.target.value })}
                placeholder="LoTW website password"
                className="text-xs"
              />
              <Label className="text-sm">TQSL path</Label>
              <Input
                value={lotw.tqsl_path}
                onChange={(e) => setLotw({ tqsl_path: e.target.value })}
                placeholder="C:\Program Files (x86)\TrustedQSL\tqsl.exe"
                className="font-mono text-xs"
              />
              <Label className="text-sm">Station location</Label>
              <div className="flex items-center gap-2">
                <Select value={lotw.station_location || '_'} onValueChange={(v) => setLotw({ station_location: v === '_' ? '' : v })}>
                  <SelectTrigger className="h-8 flex-1"><SelectValue placeholder="— pick a TQSL location —" /></SelectTrigger>
                  <SelectContent>
                    {stationLocations.length === 0 && <SelectItem value="_" disabled>No TQSL locations found</SelectItem>}
                    {stationLocations.map((n) => <SelectItem key={n} value={n}>{n}</SelectItem>)}
                  </SelectContent>
                </Select>
                <Button variant="ghost" size="sm" className="h-8 px-2" onClick={refreshLocations} title="Reload locations from TQSL">
                  <ArrowDown className="size-3.5 rotate-90" />
                </Button>
              </div>
              <Label className="text-sm">Key password</Label>
              <Input
                type="password"
                value={lotw.key_password}
                onChange={(e) => setLotw({ key_password: e.target.value })}
                placeholder="only if your certificate key has a password"
                className="text-xs"
              />
              <Label className="text-sm">Upload flag</Label>
              <div>
                <Select value={lotw.upload_flag || 'R'} onValueChange={(v) => setLotw({ upload_flag: v })}>
                  <SelectTrigger className="h-8 w-64"><SelectValue /></SelectTrigger>
                  <SelectContent>
                    <SelectItem value="N">Upload when LoTW sent = No</SelectItem>
                    <SelectItem value="R">Upload when LoTW sent = Requested</SelectItem>
                  </SelectContent>
                </Select>
                <div className="text-[10px] text-muted-foreground mt-1">
                  Must match your default LoTW <em>sent</em> status in Confirmations, or new QSOs won't be picked up.
                </div>
              </div>
            </div>

            <div className="border-t border-border/60 pt-3 space-y-3">
              <label className="flex items-center gap-2 text-sm cursor-pointer">
                <Checkbox
                  checked={lotw.auto_upload}
                  onCheckedChange={(c) => setLotw({ auto_upload: !!c, upload_mode: 'on_close' })}
                />
                Automatic upload on application close
              </label>
              <label className="flex items-center gap-2 text-sm cursor-pointer">
                <Checkbox
                  checked={lotw.write_log}
                  onCheckedChange={(c) => setLotw({ write_log: !!c })}
                />
                Write TQSL diagnostic log (-t)
              </label>

              <div className="flex items-center gap-3">
                <Button variant="outline" size="sm" onClick={testLotw} disabled={lotwTesting}>
                  <UploadCloud className="size-3.5" /> {lotwTesting ? 'Testing…' : 'Test connection'}
                </Button>
                {lotwTest && (
                  <span className={cn('text-xs', lotwTest.ok ? 'text-emerald-700' : 'text-rose-700')}>
                    {lotwTest.msg}
                  </span>
                )}
              </div>
            </div>
          </div>
        ) : (
          <div className="flex flex-col items-center justify-center text-muted-foreground gap-2 py-16">
            <Construction className="size-10 opacity-30" />
            <div className="text-sm font-semibold text-foreground/70">
              {TABS.find((t) => t.k === extSvcTab)?.label} — coming soon
            </div>
            <div className="text-xs">This external service isn't wired up yet.</div>
          </div>
        )}
      </>
    );
  }

  function DatabasePanel() {
    async function refreshDb() { try { setDbSettings(await GetDatabaseSettings() as any); } catch {} }
    async function openExisting() {
      try {
        const p = await PickOpenDatabase();
        if (!p) return;
        await OpenDatabase(p);
        await refreshDb();
        setDbMsg(`Database set to:\n${p}\nRestart OpsLog to apply.`);
      } catch (e: any) { setErr(String(e?.message ?? e)); }
    }
    async function saveCopy() {
      try {
        const p = await PickSaveDatabase();
        if (!p) return;
        await MoveDatabase(p);
        await refreshDb();
        setDbMsg(`A copy was saved to:\n${p}\nand selected. Restart OpsLog to apply.`);
      } catch (e: any) { setErr(String(e?.message ?? e)); }
    }
    async function resetDefault() {
      try {
        await ResetDatabaseToDefault();
        await refreshDb();
        setDbMsg('Database reset to the default location. Restart OpsLog to apply.');
      } catch (e: any) { setErr(String(e?.message ?? e)); }
    }
    return (
      <>
        <SectionHeader
          title="Database location"
          hint="Keep your log database wherever you like — another drive or a synced folder (Seafile, Dropbox…) — so it survives a Windows reinstall. Everything (QSOs, settings, lookup cache) lives in this one file."
        />
        <div className="space-y-4 max-w-2xl">
          <div className="space-y-1">
            <Label>Current database</Label>
            <div className="font-mono text-xs bg-muted/40 border border-border rounded-md px-3 py-2 break-all">
              {dbSettings.path || '—'}
              {dbSettings.is_custom
                ? <span className="ml-2 text-[10px] text-emerald-700">(custom location)</span>
                : <span className="ml-2 text-[10px] text-muted-foreground">(default)</span>}
            </div>
            <div className="text-[10px] text-muted-foreground">Default: <span className="font-mono">{dbSettings.default_path}</span></div>
          </div>

          <div className="flex flex-wrap gap-2">
            <Button variant="outline" size="sm" onClick={openExisting}>Open existing database…</Button>
            <Button variant="outline" size="sm" onClick={saveCopy}>Save a copy &amp; switch to it…</Button>
            {dbSettings.is_custom && <Button variant="ghost" size="sm" onClick={resetDefault}>Reset to default</Button>}
          </div>

          <div className="text-[11px] text-muted-foreground bg-amber-50 border border-amber-200 rounded-md p-2.5 leading-relaxed">
            <strong>Open existing</strong> points OpsLog at a database file you already have (e.g. after reinstalling Windows).{' '}
            <strong>Save a copy</strong> writes the current database to a new place and switches to it.{' '}
            A database change takes effect on the next launch.
          </div>

          {dbMsg && (
            <div className="text-xs bg-emerald-50 border border-emerald-300 text-emerald-800 rounded-md px-3 py-2 flex items-center justify-between gap-3 whitespace-pre-line">
              <span>{dbMsg}</span>
              <Button size="sm" variant="destructive" className="shrink-0" onClick={() => QuitApp()}>Quit now</Button>
            </div>
          )}
        </div>
      </>
    );
  }

  // Map sections to their content + icon (for placeholder).
  const PANELS: Record<SectionId, () => JSX.Element> = {
    station:        StationPanel,
    profiles:       ProfilesPanel,
    operating:      OperatingPanelWrapper,
    confirmations:  ConfirmationsPanel,
    'external-services': ExternalServicesPanel,
    lookup:         LookupPanel,
    'lists-bands':  BandsPanel,
    'lists-modes':  ModesPanel,
    cluster:        ClusterPanel,
    udp:            UDPIntegrationsPanelWrapper,
    backup:         BackupPanel,
    database:       DatabasePanel,
    awards:         () => <ComingSoon id="awards" icon={Award} />,
    cat:            CATPanel,
    rotator:        RotatorPanel,
    antenna:        () => <ComingSoon id="antenna" icon={AntennaIcon} />,
    audio:          () => <ComingSoon id="audio" icon={Server} />,
  };

  return (
    <Dialog open onOpenChange={(o) => { if (!o) onClose(); }}>
      <DialogContent className="max-w-[1180px] w-full max-h-[90vh] grid grid-rows-[auto_1fr_auto] gap-0 p-0">
        <DialogHeader>
          <DialogTitle>Preferences</DialogTitle>
          <DialogDescription className="sr-only">Configure OpsLog modules — station, lookup, hardware…</DialogDescription>
        </DialogHeader>

        {loading ? (
          <div className="p-6 text-muted-foreground">Loading…</div>
        ) : (
          <div className="grid grid-cols-[320px_1fr] min-h-0 overflow-hidden">
            {/* Left sidebar tree */}
            <aside className="border-r border-border bg-muted/30 overflow-y-auto p-2">
              <Tree selected={selected} onSelect={setSelected} />
            </aside>

            {/* Right content pane */}
            <div className="overflow-y-auto p-6">
              <div className="text-[10px] uppercase tracking-wider text-muted-foreground mb-3 font-semibold">
                {breadcrumb}
              </div>
              {PANELS[selected]?.()}

              {err && (
                <div className="mt-6 text-xs text-destructive bg-destructive/10 border border-destructive/30 rounded-md px-3 py-2 max-w-2xl">
                  {err}
                </div>
              )}
              {msg && (
                <div className="mt-6 text-xs rounded-md px-3 py-2 max-w-2xl text-[color:var(--color-ok)] bg-[color:var(--color-ok)]/10 border border-[color:var(--color-ok)]/30">
                  {msg}
                </div>
              )}
            </div>
          </div>
        )}

        <DialogFooter>
          <Button variant="outline" onClick={onClose} disabled={saving}>Cancel</Button>
          <Button onClick={save} disabled={saving || loading}>{saving ? 'Saving…' : 'Save all'}</Button>
        </DialogFooter>
      </DialogContent>
    </Dialog>
  );
}

// ClusterServerEditor edits one row of cluster_servers. Init commands are
// free-form (one per line); the backend strips blanks and "//" comments.
interface ClusterEditorProps {
  value: Omit<clusterModels.ServerConfig, 'convertValues'>;
  onCancel: () => void;
  onSave: (s: Omit<clusterModels.ServerConfig, 'convertValues'>) => void | Promise<void>;
}

function ClusterServerEditor({ value, onCancel, onSave }: ClusterEditorProps) {
  const [s, setS] = useState(value);
  const update = (patch: Partial<typeof s>) => setS((cur) => ({ ...cur, ...patch }));
  return (
    <Dialog open onOpenChange={(o) => { if (!o) onCancel(); }}>
      <DialogContent className="max-w-[640px] px-6">
        <DialogHeader className="px-2">
          <DialogTitle>{s.id ? `Edit cluster · ${s.name || 'unnamed'}` : 'New cluster'}</DialogTitle>
          <DialogDescription className="text-xs">
            Telnet endpoint + optional login override and init commands. Init commands are sent one per line, 0.5s apart, right after login.
          </DialogDescription>
        </DialogHeader>
        <div className="grid grid-cols-2 gap-3 py-2 px-2">
          <div className="space-y-1 col-span-2">
            <Label>Display name</Label>
            <Input autoFocus value={s.name} onChange={(e) => update({ name: e.target.value })} placeholder="VE7CC, F4BPO home…" />
          </div>
          <div className="space-y-1">
            <Label>Host</Label>
            <Input className="font-mono" value={s.host} onChange={(e) => update({ host: e.target.value })} placeholder="dxc.k0xm.net" />
          </div>
          <div className="space-y-1">
            <Label>Port</Label>
            <Input type="number" min={1} max={65535} className="font-mono" value={s.port} onChange={(e) => update({ port: parseInt(e.target.value) || 7300 })} />
          </div>
          <div className="space-y-1">
            <Label>Login callsign (optional)</Label>
            <Input className="font-mono uppercase" value={s.login_override} onChange={(e) => update({ login_override: e.target.value })} placeholder="Active profile if empty" />
          </div>
          <div className="space-y-1">
            <Label>Password (optional)</Label>
            <Input type="password" value={s.password} onChange={(e) => update({ password: e.target.value })} autoComplete="off" />
          </div>
          <div className="space-y-1 col-span-2">
            <Label>Init commands (one per line, // for comments)</Label>
            <Textarea
              className="font-mono text-xs min-h-[120px]"
              value={s.init_commands}
              onChange={(e) => update({ init_commands: e.target.value })}
              placeholder={`// turn on DXCC info\nset/needsdxcc\nsh/dx 30`}
            />
          </div>
          <label className="flex items-center gap-2 text-sm cursor-pointer col-span-2">
            <Checkbox checked={s.enabled} onCheckedChange={(c) => update({ enabled: !!c })} />
            Enabled (will be connected at startup if Auto-connect is on)
          </label>
        </div>
        <DialogFooter className="px-2">
          <Button variant="outline" onClick={onCancel}>Cancel</Button>
          <Button
            onClick={() => onSave({ ...s, name: s.name.trim(), host: s.host.trim(), login_override: s.login_override.trim().toUpperCase() })}
            disabled={!s.name.trim() || !s.host.trim()}
          >
            {s.id ? 'Save changes' : 'Create cluster'}
          </Button>
        </DialogFooter>
      </DialogContent>
    </Dialog>
  );
}
