// MotorAntennaWidget — the Ultrabeam / SteppIR control, in one place.
//
// Lives in its own file because it is shown in TWO: the Station Control tab and
// the dock beside the entry strip. Two copies of a control that keys hardware is
// how one of them quietly stops matching the other — a pattern button that sets
// a different direction, a band list that tunes somewhere else.
import { useCallback, useEffect, useState } from 'react';
import { ArrowDownToLine, ChevronDown, ChevronUp, Loader2, Minus, Plus, RefreshCw, Antenna as AntennaIcon, X } from 'lucide-react';
import { cn } from '@/lib/utils';
import {
  SetUltrabeamDirection, UltrabeamRetract, MotorSetElement, MotorReadElements,
  MotorTuneKHz, MotorNudgeKHz, SetMotorFollow,
} from '../../wailsjs/go/main/App';

export type AntStatus = { enabled: boolean; type: string; connected: boolean; direction: number; frequency: number; moving: boolean; elements: number[]; follow?: boolean; step_khz?: number; track_mode?: string; bands?: string[]; band_freqs?: Record<string, number> };

// Where each band button points the antenna.
//
// Not the arithmetic centre of the band. An antenna is tuned for where people
// actually work: 20 m centres on 14175 but nobody lives there, and 10 m spans
// 1.7 MHz of which the top half is empty. These are the points that leave the
// elements closest to right for the whole band, and one nudge away from the rest.
const ANT_BAND_KHZ: Record<string, number> = {
  '40m': 7100, '30m': 10125, '20m': 14150, '17m': 18110,
  '15m': 21150, '12m': 24930, '10m': 28400, '6m': 50150,
};

// NUDGE_KHZ is the up/down step. 25 kHz because that is also the finest tracking
// threshold: a nudge smaller than the tracking step would be undone by the next
// poll while tracking is on.
const NUDGE_KHZ = 25;

// bandOfKHz names the band a frequency sits in, so a band button can light up
// when the ANTENNA is already there. Deliberately generous at the edges: the
// antenna's reported frequency is where it was commanded, which can sit slightly
// outside the allocation.
function bandOfKHz(khz: number): string {
  const edges: [number, number, string][] = [
    [6900, 7300, '40m'], [10050, 10200, '30m'], [13900, 14400, '20m'],
    [18000, 18200, '17m'], [20900, 21500, '15m'], [24800, 25000, '12m'],
    [27900, 29800, '10m'], [49900, 50600, '6m'],
  ];
  for (const [lo, hi, b] of edges) if (khz >= lo && khz <= hi) return b;
  return '';
}

// MotorAntennaWidget controls a motorized antenna (Ultrabeam / SteppIR) from the
// Station Control tab: pattern (Normal / 180° / Bi), Retract, and — Ultrabeam
// only — per-element length adjustment. Heading/state is polled by the panel.
const ELEMENT_STEP = 2; // the physical console adjusts 2 mm per press

// elementName maps an element index to a ham-radio name: 0 = reflector,
// 1 = driven element, then Director 1, 2, 3…
function elementName(i: number, t: (k: string, v?: any) => string): string {
  if (i === 0) return t('station.reflector');
  if (i === 1) return t('station.driven');
  return `${t('station.director')} ${i - 1}`;
}

export function MotorAntennaWidget({ ant, refetch, t, onClose, essentialsOnly }: {
  ant: AntStatus;
  refetch: () => void;
  t: (k: string, v?: any) => string;
  // onClose adds the dock's close button. The Station Control tab passes none:
  // there the widget is a tile in a grid, and a tile that closes itself would
  // leave a hole the operator cannot fill back in.
  onClose?: () => void;
  // essentialsOnly drops what an operator sets once and then leaves alone:
  // tracking with its mode and step, and the per-element lengths. They belong in
  // Station Control, where there is room to think; in the dock they are height
  // spent on controls nobody touches between contacts, pushing the ones they do
  // touch — pattern, bands, nudge — off the bottom.
  essentialsOnly?: boolean;
}) {
  const [err, setErr] = useState('');
  const [lengths, setLengths] = useState<number[]>([]); // current element lengths (mm), from ReadElements
  const [reading, setReading] = useState(false);
  const [busyEl, setBusyEl] = useState<number | null>(null);
  const [editingEl, setEditingEl] = useState<number | null>(null); // element whose mm is being typed
  const [editVal, setEditVal] = useState('');
  const run = (p: Promise<any>) => p.then(refetch).catch((e) => setErr(String(e?.message ?? e)));
  const isUB = ant.type !== 'steppir';

  const readLengths = useCallback(async () => {
    setReading(true); setErr('');
    try { setLengths(((await MotorReadElements()) ?? []) as number[]); }
    catch (e: any) { setErr(String(e?.message ?? e)); }
    finally { setReading(false); }
  }, []);
  // Read the current lengths once when the Ultrabeam widget mounts/connects, so
  // +/- starts from the real values rather than blind.
  useEffect(() => { if (isUB && ant.connected) readLengths(); }, [isUB, ant.connected, readLengths]);

  // Send an absolute length to one element and remember it as the new baseline.
  const setLen = async (i: number, mm: number) => {
    const next = Math.max(0, Math.round(mm));
    const prev = lengths[i] ?? 0;
    setBusyEl(i); setErr('');
    setLengths((ls) => { const c = [...ls]; c[i] = next; return c; }); // optimistic
    try { await MotorSetElement(i, next); refetch(); }
    catch (e: any) {
      // Rejected by the controller — most often the element is at its travel
      // limit for this band (extending on a low band). Revert the optimistic
      // value so the display stays truthful, and explain the likely cause when
      // the failed move was an extension.
      setLengths((ls) => { const c = [...ls]; c[i] = prev; return c; });
      setErr(next > prev ? t('station.atMax') : String(e?.message ?? e));
    }
    finally { setBusyEl(null); }
  };
  // Nudge one element by ±2 mm from its current known length.
  const nudge = (i: number, delta: number) => setLen(i, (lengths[i] ?? 0) + delta);
  // Commit a typed exact length (click on the mm value). Lets the operator fix
  // the baseline when the auto-read is off, so +/- then work reliably.
  const commitEdit = (i: number) => {
    const v = parseInt(editVal, 10);
    setEditingEl(null);
    if (!isNaN(v) && v >= 0 && v !== lengths[i]) setLen(i, v);
  };
  const dirs: [number, string][] = [[0, 'N'], [1, '180°'], [2, t('station.bi')]];
  return (
    <div className="rounded-xl border border-border bg-card shadow-sm overflow-hidden h-full flex flex-col min-h-0">
      <div className="flex items-center gap-2 px-2 py-1.5 border-b border-border/60 bg-muted/30 shrink-0">
        <AntennaIcon className="size-4 text-primary" />
        <div className="min-w-0">
          <div className="text-sm font-semibold truncate">{isUB ? 'Ultrabeam' : 'SteppIR'}</div>
          {ant.frequency > 0 && <div className="text-[10px] text-muted-foreground font-mono">{(ant.frequency / 1000).toFixed(3)} MHz</div>}
        </div>
        {ant.moving && <span className="ml-auto text-[10px] font-semibold text-warning animate-pulse">{t('station.moving')}</span>}
        <span className={cn('size-2 rounded-full shrink-0', ant.moving ? '' : 'ml-auto', ant.connected ? 'bg-success' : 'bg-muted-foreground/40')}
          title={ant.connected ? t('station.online') : t('station.offline')} />
        {onClose && (
          <button type="button" onClick={onClose} title={t('station.hideWidget')}
            className="text-muted-foreground hover:text-foreground shrink-0">
            <X className="size-3.5" />
          </button>
        )}
      </div>
      <div className="p-2 space-y-2 flex-1 min-h-0 overflow-auto">
        <div>
          {/* No heading: N / 180° / Bi say what they are, and in a docked widget
              a line of height costs more than the word explains. */}
          <div className="flex gap-1">
            {dirs.map(([d, lbl]) => (
              <button key={d} type="button" disabled={!ant.connected}
                onClick={() => run(SetUltrabeamDirection(d))}
                className={cn('flex-1 rounded-md border py-1 text-xs font-semibold transition-colors disabled:opacity-40',
                  ant.direction === d ? 'bg-primary text-primary-foreground border-primary' : 'border-border hover:bg-muted')}>
                {lbl}
              </button>
            ))}
          </div>
        </div>
        {/* Bands, then a nudge, then tracking — in the order an operator uses
            them: get to the band, fine-tune inside it, decide whether the
            antenna should follow the rig from here. */}
        <div>
          <div className="text-[10px] font-semibold uppercase tracking-wider text-muted-foreground mb-0.5">{t('station.bands')}</div>
          <div className="grid grid-cols-5 gap-1">
            {(ant.bands ?? []).map((b: string) => {
              // Where this band tunes is resolved by the backend — the operator's
              // per-band choice from Settings, or the default. ANT_BAND_KHZ is only
              // the floor for a status poll that hasn't landed yet.
              const khz = ant.band_freqs?.[b] || ANT_BAND_KHZ[b];
              if (!khz) return null;
              // "On this band" from the antenna's own frequency, not the rig's:
              // the widget must show where the ANTENNA is, which is the whole
              // reason for tuning it by hand.
              const here = ant.frequency > 0 && bandOfKHz(ant.frequency) === b;
              return (
                <button key={b} type="button" disabled={!ant.connected}
                  onClick={() => run(MotorTuneKHz(khz))}
                  title={`${(khz / 1000).toFixed(3)} MHz`}
                  className={cn('rounded-md border py-0.5 text-[11px] font-mono font-semibold transition-colors disabled:opacity-40',
                    here ? 'bg-primary text-primary-foreground border-primary' : 'border-border hover:bg-muted')}>
                  {b}
                </button>
              );
            })}
          </div>
        </div>

        <div className="flex items-center gap-1">
          <button type="button" disabled={!ant.connected}
            onClick={() => run(MotorNudgeKHz(-NUDGE_KHZ))}
            title={t('station.nudgeDown', { n: NUDGE_KHZ })}
            className="flex-1 flex items-center justify-center gap-1 rounded-md border border-border py-1 text-xs font-semibold hover:bg-muted disabled:opacity-40">
            <ChevronDown className="size-3.5" /> {NUDGE_KHZ}
          </button>
          <button type="button" disabled={!ant.connected}
            onClick={() => run(MotorNudgeKHz(NUDGE_KHZ))}
            title={t('station.nudgeUp', { n: NUDGE_KHZ })}
            className="flex-1 flex items-center justify-center gap-1 rounded-md border border-border py-1 text-xs font-semibold hover:bg-muted disabled:opacity-40">
            <ChevronUp className="size-3.5" /> {NUDGE_KHZ}
          </button>
        </div>

        {/* Tracking. Here rather than only in Settings because it is an operating
            decision — off to park the antenna, on to resume — not something set
            up once. Mode and step only show when tracking is on: settings for
            something switched off are questions the operator cannot act on. And
            the step only shows in step mode, where it is the one thing it means. */}
        {!essentialsOnly && (
        <div className="space-y-1.5">
          <div className="flex items-center gap-2">
            <button type="button"
              onClick={() => run(SetMotorFollow(!ant.follow, 0, ''))}
              className={cn('flex-1 rounded-md border py-1 text-xs font-semibold transition-colors',
                ant.follow ? 'bg-success-muted text-success-muted-foreground border-success-border' : 'border-border hover:bg-muted')}>
              {ant.follow ? t('station.trackOn') : t('station.trackOff')}
            </button>
            {ant.follow && (ant.track_mode || 'step') === 'step' && (
              <select
                value={String(ant.step_khz || 50)}
                onChange={(e) => run(SetMotorFollow(true, parseInt(e.target.value, 10), ''))}
                className="h-[30px] rounded-md border border-border bg-background px-1 text-xs font-mono"
                title={t('station.trackStepTip')}
              >
                {[25, 50, 100].map((s) => <option key={s} value={s}>{s} kHz</option>)}
              </select>
            )}
          </div>
          {ant.follow && (
            <select
              value={ant.track_mode || 'step'}
              onChange={(e) => run(SetMotorFollow(true, 0, e.target.value))}
              className="w-full h-[30px] rounded-md border border-border bg-background px-1.5 text-xs"
              title={t('station.trackModeTip')}
            >
              <option value="always" title={t('station.trackAlwaysTip')}>{t('station.trackAlways')}</option>
              <option value="step" title={t('station.trackStepTipMode')}>{t('station.trackStep')}</option>
              <option value="band" title={t('station.trackBandTip')}>{t('station.trackBand')}</option>
            </select>
          )}
        </div>
        )}

        <button type="button" disabled={!ant.connected}
          onClick={() => run(UltrabeamRetract())}
          className="w-full flex items-center justify-center gap-1.5 rounded-md border border-warning-border bg-warning-muted text-warning-muted-foreground py-1 text-xs font-semibold hover:brightness-95 disabled:opacity-40">
          <ArrowDownToLine className="size-3.5" /> {t('station.retract')}
        </button>

        {isUB && !essentialsOnly && (
          <div>
            <div className="flex items-center gap-2 mb-1.5">
              <span className="text-[10px] font-semibold uppercase tracking-wider text-muted-foreground">{t('station.elements')}</span>
              <button type="button" onClick={readLengths} disabled={!ant.connected || reading}
                className="text-[10px] text-primary hover:underline inline-flex items-center gap-1 disabled:opacity-40" title={t('station.readLengths')}>
                <RefreshCw className={cn('size-3', reading && 'animate-spin')} /> {t('station.read')}
              </button>
            </div>
            {lengths.length === 0 ? (
              <p className="text-[11px] text-muted-foreground">{t('station.noLengths')}</p>
            ) : (
              <div className="space-y-1.5">
                {/* The READ_BANDS reply is undocumented and its 16-bit parse picks
                    up structural bytes past the real data (varying by band), which
                    made 6+ bogus "elements" appear. Ultrabeam beams are 3-element,
                    and ModifyElement addresses elements 0..2 — so show just those. */}
                {lengths.map((mm, i) => ({ mm, i })).slice(0, 3).map(({ mm, i }) => (
                  <div key={i} className="flex items-center gap-2">
                    <span className="text-xs w-20 shrink-0 text-muted-foreground truncate">{elementName(i, t)}</span>
                    <button type="button" disabled={!ant.connected || busyEl !== null}
                      onClick={() => nudge(i, -ELEMENT_STEP)}
                      className="flex items-center justify-center size-7 rounded-md border border-border hover:bg-muted disabled:opacity-40 shrink-0">
                      <Minus className="size-3.5" />
                    </button>
                    {editingEl === i ? (
                      <input autoFocus type="number" value={editVal}
                        onChange={(e) => setEditVal(e.target.value)}
                        onBlur={() => commitEdit(i)}
                        onKeyDown={(e) => { if (e.key === 'Enter') commitEdit(i); if (e.key === 'Escape') setEditingEl(null); }}
                        className="text-sm font-mono font-bold flex-1 min-w-0 text-center tabular-nums bg-transparent border border-primary rounded px-1" />
                    ) : (
                      <span className="text-sm font-mono font-bold flex-1 min-w-0 text-center tabular-nums cursor-pointer hover:underline"
                        title={t('station.setExactLen')}
                        onClick={() => { setEditingEl(i); setEditVal(String(mm)); }}>
                        {busyEl === i ? <Loader2 className="size-3.5 animate-spin inline" /> : `${mm} mm`}
                      </span>
                    )}
                    <button type="button" disabled={!ant.connected || busyEl !== null}
                      onClick={() => nudge(i, ELEMENT_STEP)}
                      className="flex items-center justify-center size-7 rounded-md border border-border hover:bg-muted disabled:opacity-40 shrink-0">
                      <Plus className="size-3.5" />
                    </button>
                  </div>
                ))}
              </div>
            )}
            <p className="text-[10px] text-muted-foreground mt-1">{t('station.elementsHint')}</p>
          </div>
        )}
        {err && <div className="text-[11px] text-destructive break-words">{err}</div>}
      </div>
    </div>
  );
}
