// sMeterRST turns an S-meter reading into the RST report you SEND to the other
// station (RST tx / rst_sent) — because your S-meter is how strong THEY are.
//
//   s      : S-unit 0-9 (used when below S9)
//   overDb : dB over S9 (0 when at/below S9)
//   mode   : operating mode — CW/digital get a 3-digit RST, phone a 2-digit RS
//
// For S9+ the dB is rounded UP to the nearest 5 (a 9+3 reading reports 59+5,
// 9+12 reports 59+15, …), matching how operators give strong-signal reports.
export function sMeterRST(s: number, overDb: number, mode?: string): string {
  const cw = /CW|RTTY|PSK|FT[0-9]|JT|JS8|MFSK|OLIVIA|DATA|DIG/i.test(mode || '');
  const strength = Math.max(1, Math.min(9, Math.round(s)));
  if (cw) return overDb > 0 ? '599' : `5${strength}9`;
  if (overDb > 0) return `59+${Math.ceil(overDb / 5) * 5}`;
  return `5${strength}`;
}

// RST dropdown lists, shared by the entry form and the QSO editor so both offer
// the same per-mode choices (Settings → Modes → RST report lists).
export type RSTLists = { phone: string[]; cw: string[]; digital: string[] };

// rstCategory maps an ADIF mode to its RST family (phone / cw / digital).
export function rstCategory(mode: string): keyof RSTLists {
  const m = (mode || '').toUpperCase();
  const digital = ['FT8', 'FT4', 'JT65', 'JT9', 'JS8', 'Q65', 'MSK144', 'FST4', 'FST4W', 'MFSK', 'OLIVIA', 'JT4', 'WSPR'];
  if (digital.includes(m)) return 'digital';
  if (['CW', 'RTTY', 'PSK31', 'PSK63', 'PSK', 'PSK125'].includes(m)) return 'cw';
  return 'phone';
}

// rstOptions returns the valid report choices for a mode from the user's
// editable lists, with a tiny fallback before they load.
export function rstOptions(mode: string, lists: RSTLists): string[] {
  const cat = rstCategory(mode);
  const l = lists[cat];
  if (l && l.length) return l;
  return cat === 'phone' ? ['59', '58', '57'] : cat === 'cw' ? ['599', '589', '579'] : ['+00', '-10', '-20'];
}

// stepRST moves a report one step up or down, the way an operator would say it.
//
// The dropdown beside these fields is a list of the values worth having to hand,
// not of every legal report: nobody keeps 41 dB figures in it. So the wheel
// works on the VALUE, not on the list — a digital report moves by a decibel and
// an RST by one S-unit, which is what the hand on the wheel is asking for.
//
// dir is +1 for wheel up (a better report) and -1 for down.
export function stepRST(value: string, dir: number, mode: string): string {
  const v = (value || '').trim();
  if (v === '') return v;
  if (rstCategory(mode) === 'digital') {
    // A dB report: "-12", "+05", "0". Kept in its own shape — signed and two
    // digits — because that is how every decoder writes it and how the operator
    // reads it back off the screen.
    const n = parseInt(v, 10);
    if (!Number.isFinite(n)) return v;
    // The range WSJT-X itself reports in, with room either side. Beyond it the
    // number stops meaning anything.
    const next = Math.max(-30, Math.min(35, n + dir));
    return (next < 0 ? '-' : '+') + String(Math.abs(next)).padStart(2, '0');
  }
  // RST/RS, and the S9+ ladder above it. What an operator counts through is
  //
  //   … 57  58  59  59+5  59+10  59+15  59+20 …
  //
  // — one S-unit up to nine, then five decibels at a time, because that is how
  // a strong signal is reported and how the S-meter is read (see sMeterRST).
  // Stepping the S digit and leaving the "+20" where it was would have gone
  // from 59+20 to 58+20, which nobody has ever said on the air.
  //
  // R and T do not move: they are judgements about readability and tone, and a
  // wheel has no business changing them.
  const m = /^(\d)(\d)(\d?)(?:\+(\d+))?$/.exec(v);
  if (!m) return v;
  const head = m[1];
  const tone = m[3];
  let sUnit = parseInt(m[2], 10);
  let over = m[4] === undefined ? 0 : parseInt(m[4], 10);
  if (over > 0) {
    // Above S9: five at a time, and back down through +5 to a plain 59.
    over = Math.max(0, Math.min(60, over + dir * 5));
  } else if (sUnit >= 9 && dir > 0) {
    over = 5; // 59 → 59+5
  } else {
    sUnit = Math.max(1, Math.min(9, sUnit + dir));
  }
  return head + String(sUnit) + tone + (over > 0 ? '+' + over : '');
}

// rstFitsMode says whether a report belongs to the family a mode reports in.
//
// The three families are written differently and are not interchangeable: a
// signed decibel figure ("+00"), a three-figure RST ("599"), a two-figure RS
// ("59", with an optional "+20" above S9). "+00" on SSB is not a weak report,
// it is not a report at all.
export function rstFitsMode(value: string, mode: string): boolean {
  const v = (value || '').trim();
  if (v === '') return false;
  switch (rstCategory(mode)) {
    case 'digital': return /^[+-]\d{1,2}$/.test(v);
    case 'cw': return /^\d{3}(\+\d+)?$/.test(v);
    default: return /^\d{2}(\+\d+)?$/.test(v);
  }
}

// convertRST carries a report across the CW/phone divide, keeping the operator's
// own judgement of the signal: 57 becomes 579, 599 becomes 59.
//
// Only between those two — a decibel figure says nothing about readability and
// an RST says nothing in decibels, so there the preset is the honest answer.
// Returns '' when it cannot be done.
export function convertRST(value: string, toMode: string): string {
  const v = (value || '').trim();
  const to = rstCategory(toMode);
  if (to === 'digital') return '';
  const m = /^(\d)(\d)(\d?)((?:\+\d+)?)$/.exec(v);
  if (!m) return '';
  if (to === 'cw') return m[1] + m[2] + '9' + m[4];  // RS → RST, tone 9
  return m[1] + m[2] + m[4];                        // RST → RS, tone dropped
}
