/**
 * LANE-UI — THE TUNER DOCK (order 9). The radio's face.
 *
 * Hidden until the optical fossil is excavated (`snapshot.relics[].found` or a
 * `relicFound` event). Then it slides in bottom-right with a frequency dial, band select
 * (AM/FM/SW), volume + mute, and a station readout in the house voice. Every control
 * drives THE SPEAKER through the resolved `RadioControl` (radio.ts); the readout reflects
 * the control's own state via `subscribe`, so it's honest whether the real engine or the
 * echo stand-in is attached.
 *
 * Radio state is per-session (it lives in the control); the durable part is the fossil's
 * `found`, which is sim-side.
 */
import { cls, el, panel, style, text } from './dom';
import { BAND_RANGE, resolveRadio, type Band, type RadioControl, type RadioState } from './radio';
import { COPY, stationReadout } from './voice';

export interface Tuner {
  el: HTMLElement;
  /** Reveal the dock (discovery ceremony). Idempotent. */
  reveal(): void;
  isRevealed(): boolean;
  /** Keyboard: ← → tune, B band, M mute — active only once revealed. */
  onKey(key: string): boolean;
  /** Whether the dock is driving the real audio engine vs the silent stand-in. */
  isLive(): boolean;
}

const BANDS: Band[] = ['AM', 'FM', 'SW'];

export function createTuner(): Tuner {
  const { control, live } = resolveRadio();

  const root = panel();
  root.id = 'fk-tuner';

  const head = el('div', 'fk-h', [COPY.tunerHeader]);
  const readout = el('div', 'fk-tuner-readout');

  // Frequency dial: a click/drag track. The fill shows position within the band, and the
  // ticks layer marks where the real stations sit (order 2). Ticks say THAT a station is
  // there, never WHAT — the readout is the only thing that names a lock, so the hunt stays.
  const dialFill = el('div', 'fk-tuner-dial-fill');
  const dialTicks = el('div', 'fk-tuner-ticks');
  const dial = el('div', 'fk-tuner-dial', [dialTicks, dialFill]);

  // Band buttons.
  const bandRow = el('div', 'fk-tuner-bands');
  const bandBtns = BANDS.map((b) => {
    const btn = el('button', 'fk-tuner-band', [b]);
    btn.setAttribute('type', 'button');
    btn.addEventListener('click', () => control.setBand(b));
    bandRow.append(btn);
    return { band: b, btn };
  });

  // Volume + mute.
  const volFill = el('div', 'fk-tuner-vol-fill');
  const volTrack = el('div', 'fk-tuner-vol', [volFill]);
  const muteBtn = el('button', 'fk-tuner-mute', [COPY.tunerMute]);
  muteBtn.setAttribute('type', 'button');
  const volRow = el('div', 'fk-tuner-volrow', [
    el('span', 'fk-tuner-vol-label', [COPY.tunerVolLabel]), volTrack, muteBtn,
  ]);

  const hint = el('div', 'fk-tuner-hint', [COPY.tunerHint]);

  root.append(head, readout, dial, bandRow, volRow, hint);

  // ---------------------------------------------------------------- interaction

  /** Set frequency from a 0..1 position within the current band. */
  function tuneToFraction(f: number, band: Band) {
    const { min, max } = BAND_RANGE[band];
    control.setFrequency(min + Math.max(0, Math.min(1, f)) * (max - min));
  }

  function dialFractionFromEvent(clientX: number): number {
    const r = dial.getBoundingClientRect();
    return r.width > 0 ? (clientX - r.left) / r.width : 0;
  }

  let dragging = false;
  dial.addEventListener('pointerdown', (e) => {
    dragging = true;
    dial.setPointerCapture?.(e.pointerId);
    tuneToFraction(dialFractionFromEvent(e.clientX), control.getState().band);
  });
  dial.addEventListener('pointermove', (e) => {
    if (dragging) tuneToFraction(dialFractionFromEvent(e.clientX), control.getState().band);
  });
  const endDrag = () => { dragging = false; };
  dial.addEventListener('pointerup', endDrag);
  dial.addEventListener('pointercancel', endDrag);

  volTrack.addEventListener('pointerdown', (e) => {
    const r = volTrack.getBoundingClientRect();
    if (r.width > 0) control.volume((e.clientX - r.left) / r.width);
  });
  muteBtn.addEventListener('click', () => control.mute(!control.getState().muted));

  function step(dir: number) {
    const s = control.getState();
    const { min, max, step } = BAND_RANGE[s.band];
    control.setFrequency(Math.max(min, Math.min(max, s.freqMHz + dir * step)));
  }
  function cycleBand(dir: number) {
    const s = control.getState();
    const i = (BANDS.indexOf(s.band) + dir + BANDS.length) % BANDS.length;
    control.setBand(BANDS[i]);
  }

  // ---------------------------------------------------------------- render from state

  /** Station tick marks for the current band. Rebuilt only when the band or the station
   *  set changes — the list is empty until the engine attaches on first gesture, then
   *  fills in on the next state push, so this lights up exactly when audio comes alive. */
  let ticksSig = '';
  function renderTicks(band: Band) {
    const stations = (control.listStations?.() ?? []).filter((st) => st.band === band);
    const { min, max } = BAND_RANGE[band];
    const sig = band + '|' + stations.map((st) => st.freqMHz.toFixed(3)).join(',');
    if (sig === ticksSig) return;
    ticksSig = sig;
    dialTicks.replaceChildren();
    if (max <= min) return;
    for (const st of stations) {
      const frac = Math.max(0, Math.min(1, (st.freqMHz - min) / (max - min)));
      const tick = el('div', 'fk-tuner-tick');
      style(tick, 'left', `${frac * 100}%`);
      dialTicks.append(tick);
    }
  }

  function render(s: RadioState) {
    text(readout, stationReadout(s.freqMHz, s.band, s.station));
    cls(readout, 'is-locked', s.station !== null);

    renderTicks(s.band);

    const { min, max } = BAND_RANGE[s.band];
    const frac = max > min ? (s.freqMHz - min) / (max - min) : 0;
    style(dialFill, 'width', `${Math.max(0, Math.min(1, frac)) * 100}%`);

    for (const b of bandBtns) cls(b.btn, 'is-active', b.band === s.band);

    style(volFill, 'width', `${s.volume * 100}%`);
    cls(muteBtn, 'is-on', s.muted);
    text(muteBtn, s.muted ? COPY.tunerMuted : COPY.tunerMute);
    cls(root, 'is-muted', s.muted);
  }
  const unsubscribe = control.subscribe(render);
  void unsubscribe; // dock lives for the session; nothing to tear down

  let revealed = false;

  return {
    el: root,
    reveal() {
      if (revealed) return;
      revealed = true;
      cls(root, 'is-revealed', true);
    },
    isRevealed: () => revealed,
    onKey(key) {
      if (!revealed) return false;
      switch (key) {
        case 'arrowleft': step(-1); return true;
        case 'arrowright': step(1); return true;
        case 'b': cycleBand(1); return true;
        case 'm': control.mute(!control.getState().muted); return true;
        default: return false;
      }
    },
    isLive: () => live,
  };
}

export type { RadioControl };
