// Shared helpers used by the cluster table and the band map — keeps the
// mode-inference logic and the status-cache key in one place so both
// surfaces always read the same data.

export function cleanSpotter(s: string): string {
  if (!s) return '';
  const i = s.indexOf('-');
  return i > 0 ? s.slice(0, i) : s;
}

// inferSpotMode picks an ADIF mode for a cluster spot. Comment text is
// the strongest hint (skimmers say "FT8", "CW 24 WPM", "RTTY"); when it
// fails we fall back to the IARU R1 band-plan segment for the frequency.
// Returns '' when nothing matches — caller should leave the rig mode
// alone instead of guessing wrong.
export function inferSpotMode(comment: string, freqHz: number): string {
  const c = (comment || '').toUpperCase();
  // SuperFox and Fox/Hound are FT8 — they are WSJT-X's DXpedition transmit modes,
  // not modes of their own, and they turn up in a comment written every way an
  // operator can shorten them: "super fox", SFOX, S/F, F/H.
  //
  // A spot commented "super fox" fell through to the band plan and came out
  // DATA, and that verdict is not cosmetic: the
  // band+mode status is computed from this answer, so a ZD8 on 21.071 read as a
  // new DATA slot rather than the new FT8 one it is.
  if (/\bSUPER\s*FOX\b|\bSFOX\b|\bS\/F\b|\bFOX\s*\/?\s*HOUND\b|\bF\/H\b/.test(c)) return 'FT8';
  if (/\bFT8\b/.test(c))   return 'FT8';
  if (/\bFT4\b/.test(c))   return 'FT4';
  if (/\bJS8\b/.test(c))   return 'JS8';
  if (/\bQ65\b/.test(c))   return 'Q65';
  if (/\bMSK144\b/.test(c)) return 'MSK144';
  if (/\bJT65\b/.test(c))  return 'JT65';
  if (/\bJT9\b/.test(c))   return 'JT9';
  if (/\bRTTY\b/.test(c))  return 'RTTY';
  if (/\bPSK(63|125|250|500)\b/.test(c)) return RegExp.$1 ? `PSK${RegExp.$1}` : 'PSK31';
  if (/\bPSK31?\b/.test(c)) return 'PSK31';
  if (/\bOLIVIA\b/.test(c)) return 'OLIVIA';
  if (/\bMFSK\b/.test(c))  return 'MFSK16';
  if (/\bCW\b/.test(c) || /\bWPM\b/.test(c)) return 'CW';
  if (/\bFM\b/.test(c))    return 'FM';
  if (/\bAM\b/.test(c))    return 'AM';
  if (/\b(SSB|USB|LSB)\b/.test(c)) return 'SSB';

  const mhz = freqHz / 1_000_000;
  type Seg = [number, number, string];
  const segs: Seg[] = [
    [1.8, 1.838, 'CW'], [1.838, 1.84, 'FT8'], [1.84, 2.0, 'SSB'],
    [3.573, 3.575, 'FT8'], [3.575, 3.578, 'FT4'], [3.5, 3.58, 'CW'], [3.58, 3.6, 'DATA'], [3.6, 4.0, 'SSB'],
    [5.357, 5.36, 'FT8'], [5.3, 5.5, 'SSB'],
    [7.0, 7.04, 'CW'], [7.074, 7.077, 'FT8'], [7.0475, 7.0485, 'FT4'],
    [7.04, 7.1, 'DATA'], [7.1, 7.3, 'SSB'],
    [10.136, 10.139, 'FT8'], [10.14, 10.143, 'FT4'], [10.1, 10.13, 'CW'], [10.13, 10.15, 'DATA'],
    [14.0, 14.07, 'CW'], [14.074, 14.077, 'FT8'], [14.08, 14.0815, 'FT4'],
    [14.07, 14.1, 'DATA'], [14.1, 14.35, 'SSB'],
    [18.1, 18.103, 'FT8'], [18.104, 18.107, 'FT4'], [18.068, 18.095, 'CW'], [18.095, 18.11, 'DATA'], [18.11, 18.168, 'SSB'],
    [21.0, 21.07, 'CW'], [21.074, 21.077, 'FT8'], [21.14, 21.143, 'FT4'],
    [21.07, 21.15, 'DATA'], [21.15, 21.45, 'SSB'],
    [24.915, 24.918, 'FT8'], [24.919, 24.922, 'FT4'], [24.89, 24.915, 'CW'], [24.915, 24.94, 'DATA'], [24.94, 24.99, 'SSB'],
    [28.0, 28.07, 'CW'], [28.074, 28.077, 'FT8'], [28.18, 28.183, 'FT4'],
    [28.07, 28.3, 'DATA'], [28.3, 29.7, 'SSB'],
    [50.0, 50.1, 'CW'], [50.313, 50.316, 'FT8'], [50.318, 50.321, 'FT4'],
    [50.1, 50.5, 'SSB'],
    [144.0, 144.15, 'CW'], [144.174, 144.177, 'FT8'], [144.15, 144.5, 'SSB'],
  ];
  for (const [lo, hi, m] of segs) {
    if (mhz >= lo && mhz < hi) return m;
  }
  return '';
}

// spotModeCategory buckets the fine-grained mode from inferSpotMode into
// the three families the cluster filter exposes: 'SSB' (phone: SSB/FM/AM),
// 'CW', and 'DATA' (every digital mode). Returns '' when the mode is
// unknown so callers can decide how to treat un-categorisable spots.
export function spotModeCategory(mode: string): 'SSB' | 'CW' | 'DATA' | '' {
  const m = (mode || '').toUpperCase();
  if (m === '') return '';
  if (m === 'CW') return 'CW';
  if (m === 'SSB' || m === 'USB' || m === 'LSB' || m === 'FM' || m === 'AM') return 'SSB';
  // Everything else inferSpotMode can return (FT8/FT4/JS8/RTTY/PSK*/…/DATA)
  // is a digital mode.
  return 'DATA';
}

// spotStatusKey is the cache key for ClusterSpotStatuses results. Must be
// computed identically in the fetcher and every reader — including the
// band map and the spot table — so a CW spot's status doesn't get looked
// up under an empty-mode key (which always misses → false "new-slot").
export function spotStatusKey(call: string, band: string, comment: string, freqHz: number): string {
  return `${call}|${band}|${inferSpotMode(comment, freqHz)}`;
}
