// Two operator options that change how a spot LOOKS, shared by the DX-cluster
// list and the band map so the two panels can never disagree about the same
// spot — the lesson already learnt with the marker colours.
//
//   muteWorked    — a station already worked gets no colour and no badge. It
//                   stays in the list, it simply stops competing for attention.
//   slotHighlight — a callsign not yet worked on THIS band and mode is coloured,
//                   whatever the entity says. For an operator filling slots, a
//                   common entity on a new band+mode is the whole point, and the
//                   entity-level status calls it "worked".
//
// They compose deliberately: mute what is done, light up what is not.

export type SpotDisplayOptions = {
  muteWorked: boolean; slotHighlight: boolean;
  // Chase switches (Settings → DX Cluster), ON by default. An operator who does
  // not chase parks does not want NEW POTA shouting from every activator spot:
  // off, the marker is withdrawn at the display layer — a "new band + new POTA"
  // spot simply reads NEW BAND — and the reference column goes quiet. The facts
  // keep being computed; only the telling stops, so ticking the box back on
  // needs no rescan.
  chasePota: boolean; chaseSota: boolean;
  chaseCounty: boolean; chasePfx: boolean; chaseGrid: boolean; chaseState: boolean;
};

// chasePota/chaseSota read the switches directly — for the places that show a
// REFERENCE rather than a status (the POTA and SOTA columns).
export function chasePota(): boolean {
  try { return localStorage.getItem('opslog.chasePota') !== '0'; } catch { return true; }
}
export function chaseSota(): boolean {
  try { return localStorage.getItem('opslog.chaseSota') !== '0'; } catch { return true; }
}
export function chaseCounty(): boolean {
  try { return localStorage.getItem('opslog.chaseCounty') !== '0'; } catch { return true; }
}
export function chasePfx(): boolean {
  try { return localStorage.getItem('opslog.chasePfx') !== '0'; } catch { return true; }
}
export function chaseState(): boolean {
  try { return localStorage.getItem('opslog.chaseState') !== '0'; } catch { return true; }
}
// chaseGrid mirrors the backend "chase new grids" setting (Settings writes the
// mirror on load and on toggle) so the display layer can gate NEW GRID without
// an async round-trip per row.
export function chaseGrid(): boolean {
  try { return localStorage.getItem('opslog.chaseGrids') !== '0'; } catch { return true; }
}

// chaseAllows answers "does this operator chase this kind of thing at all?"
// for the ORTHOGONAL markers — park, square, prefix, county.
//
// The switches were written for the cluster and stayed there, so an operator
// who does not chase parks still met NEW POTA in the decode list and in Chase
// new: the same badge, withdrawn on one screen and shouting on the next. The
// setting is about what the operator hunts, not about which panel is open.
//
// The keys are both the status-entry field names (new_pota…) and the shorter
// category names the panels filter with (pota…), so one call serves both.
// Anything without a switch of its own — a new US state — is always allowed.
export function chaseAllows(key: string): boolean {
  switch (key) {
    case 'new_pota': case 'pota': return chasePota();
    case 'new_grid': case 'grid': return chaseGrid();
    case 'new_pfx': case 'pfx': return chasePfx();
    case 'new_county': case 'cty': return chaseCounty();
    case 'new_state': case 'state': return chaseState();
    default: return true;
  }
}

// Both options are withdrawn from the filter panel for now. The machinery below
// is deliberately kept whole — it is correct and hard-won — so putting the two
// switches back is this one flag and the block they came from in App.tsx.
//
// The saved preferences are left untouched in localStorage rather than cleared:
// an operator who had either turned on gets them back exactly as they were the
// day the options return, instead of silently starting from off.
export const SPOT_DISPLAY_OPTIONS_EXPOSED = false;

export function readSpotDisplayOptions(): SpotDisplayOptions {
  // The EXPOSED flag only withdraws the two original switches; the chase
  // switches are live regardless.
  if (!SPOT_DISPLAY_OPTIONS_EXPOSED) {
    return { muteWorked: false, slotHighlight: false, chasePota: chasePota(), chaseSota: chaseSota(), chaseCounty: chaseCounty(), chasePfx: chasePfx(), chaseGrid: chaseGrid(), chaseState: chaseState() };
  }
  try {
    return {
      muteWorked: localStorage.getItem('opslog.clusterMuteWorked') === '1',
      slotHighlight: localStorage.getItem('opslog.clusterSlotHighlight') === '1',
      chasePota: chasePota(), chaseSota: chaseSota(),
      chaseCounty: chaseCounty(), chasePfx: chasePfx(), chaseGrid: chaseGrid(),
      chaseState: chaseState(),
    };
  } catch {
    return { muteWorked: false, slotHighlight: false, chasePota: true, chaseSota: true, chaseCounty: true, chasePfx: true, chaseGrid: true, chaseState: true };
  }
}

type Entry = {
  status?: string;
  worked_call?: boolean;
  worked_slot?: boolean;
  new_county?: boolean;
  new_pota?: boolean;
  new_pfx?: boolean;
  new_grid?: boolean;
  new_state?: boolean;
} | undefined;

// applySpotDisplay rewrites a status entry per the options, so every consumer —
// colour, badge, status text — follows from one decision instead of each panel
// re-deriving it.
export function applySpotDisplay<T extends Entry>(s: T, o: SpotDisplayOptions): T {
  if (!s) return s;
  let e = s;

  // Slot promotion runs first: a callsign not yet worked on this band and mode
  // is not done, whatever the entity says, so it earns a status before the mute
  // below can take its colour away.
  if (o.slotHighlight && e.worked_slot === false && (!e.status || e.status === 'worked')) {
    e = { ...e, status: 'new-call' } as NonNullable<T>;
  }

  // Mute drops the blue already-worked-callsign mark, and NOTHING else.
  //
  // It used to blank the status as well, on the theory that a spot bringing no
  // novelty should stop painting entirely. That was wrong twice over. The status
  // is what the cluster list reads to DIM a row, so blanking it turned every
  // quiet grey row bright white — the option made the list louder, not quieter.
  // And an empty status means "entity not resolved" everywhere else, so muted
  // spots had to carry a flag saying they did not really mean that.
  //
  // Leaving the status alone costs nothing: a worked entity already renders with
  // no colour and gets dimmed, so removing the blue is the entire job.
  if (o.muteWorked) {
    e = { ...e, worked_call: false } as NonNullable<T>;
  }
  if (!o.chasePota && e.new_pota) {
    e = { ...e, new_pota: false } as NonNullable<T>;
  }
  // Same withdrawal for the other extra markers: the facts keep being
  // computed, only the telling stops — re-ticking a box needs no rescan.
  if (!o.chaseCounty && e.new_county) {
    e = { ...e, new_county: false } as NonNullable<T>;
  }
  if (!o.chasePfx && e.new_pfx) {
    e = { ...e, new_pfx: false } as NonNullable<T>;
  }
  if (!o.chaseGrid && e.new_grid) {
    e = { ...e, new_grid: false } as NonNullable<T>;
  }
  if (!o.chaseState && e.new_state) {
    e = { ...e, new_state: false } as NonNullable<T>;
  }
  return e;
}

// spotIsWorked answers "does this spot bring me nothing?", which is what the
// "hide worked" filter is really asking.
//
// The extra markers are ORTHOGONAL to the entity status — a spot can be a worked
// entity AND a new park, prefix, county or grid — so they win. Without that, a
// new prefix in an entity worked years ago vanished the moment the filter went
// on, which is the opposite of what an operator hunting prefixes wants.
export function spotIsWorked(e: Entry): boolean {
  if (!e) return false;
  if (e.new_pota || e.new_county || e.new_pfx || e.new_grid) return false;
  return !!e.worked_call || e.status === 'worked';
}
