/**
 * LANE-UI — the bridge to THE SPEAKER's radio control.
 *
 * Order 9's tuner dock drives the audio engine through a narrow control that LANE-SCREEN
 * exports from `src/audio` (their documented API: setFrequency / setBand / volume / mute /
 * getState / subscribe). That export does not exist yet — `src/audio` is still a stub — so
 * this module resolves the control at runtime and, until it lands, drives a local echo
 * stand-in so every dial is fully functional and testable now.
 *
 * COORDINATION (see NOTES): I resolve the control from, in order,
 *   1. `window.__fktrySpeaker.radio`  — the handshake I'm proposing (SCREEN already keeps
 *      a `window.__fktryScreen` debug hook, so this is idiomatic for them), or
 *   2. a `radio` / `getRadio()` export from `../audio` (dynamic, cast — never breaks the
 *      build if absent).
 * SCREEN: set one of those to a RadioControl and the dock drives real audio with zero
 * change here. Tell me in NOTES which name you pick.
 *
 * The stand-in is silent — it only echoes state back through `subscribe` so the readout is
 * honest about what the dock is asking for, even with no engine attached.
 */
export type Band = 'AM' | 'FM' | 'SW';

export interface RadioState {
  freqMHz: number;
  band: Band;
  volume: number; // 0..1
  muted: boolean;
  /** Station name if the current frequency locks one, else null (static). */
  station: string | null;
}

/** One station on the dial, as the speaker adapter exposes it — MHz units for every band. */
export interface StationMark {
  id: string;
  name: string;
  band: Band;
  freqMHz: number;
}

export interface RadioControl {
  setFrequency(mhz: number): void;
  setBand(band: Band): void;
  volume(v: number): void;
  mute(on: boolean): void;
  getState(): RadioState;
  subscribe(cb: (s: RadioState) => void): () => void;
  /** v8: every station on the dial, for tick marks. Empty until the engine attaches
   *  (first user gesture); the echo stand-in has none. Optional — guard the call. */
  listStations?(): StationMark[];
}

/** Band frequency ranges (MHz-ish; SW/AM are scaled for a single readable dial). */
export const BAND_RANGE: Record<Band, { min: number; max: number; step: number }> = {
  AM: { min: 0.53, max: 1.7, step: 0.01 },
  FM: { min: 88.0, max: 108.0, step: 0.1 },
  SW: { min: 5.9, max: 26.1, step: 0.05 },
};

/**
 * Local echo stand-in. Holds state, notifies subscribers, locks no stations (the real
 * station map is SCREEN's). Keeps the dock alive before the engine exists.
 */
export function createEchoRadio(): RadioControl {
  let state: RadioState = { freqMHz: BAND_RANGE.FM.min, band: 'FM', volume: 0.7, muted: false, station: null };
  const subs = new Set<(s: RadioState) => void>();
  const notify = () => { for (const cb of subs) cb(state); };

  return {
    setFrequency(mhz) { state = { ...state, freqMHz: mhz }; notify(); },
    setBand(band) { state = { ...state, band, freqMHz: BAND_RANGE[band].min }; notify(); },
    volume(v) { state = { ...state, volume: Math.max(0, Math.min(1, v)) }; notify(); },
    mute(on) { state = { ...state, muted: on }; notify(); },
    getState: () => state,
    subscribe(cb) { subs.add(cb); cb(state); return () => subs.delete(cb); },
    listStations: () => [], // the real station map is SCREEN's; the stand-in knows none
  };
}

/** Resolve the real control if THE SPEAKER has published one; else the echo stand-in. */
export function resolveRadio(): { control: RadioControl; live: boolean } {
  const speaker = (globalThis as any).__fktrySpeaker;
  if (speaker?.radio && typeof speaker.radio.subscribe === 'function') {
    return { control: speaker.radio as RadioControl, live: true };
  }
  // Dynamic, casted probe of the audio module — safe whether or not the export exists.
  const audioMod = (globalThis as any).__fktryAudioModule;
  const fromMod = audioMod?.radio ?? audioMod?.getRadio?.();
  if (fromMod && typeof fromMod.subscribe === 'function') {
    return { control: fromMod as RadioControl, live: true };
  }
  return { control: createEchoRadio(), live: false };
}
