// WatchlistWidget — the watch list reduced to what is worth acting on RIGHT
// NOW: entries that are on the air and still needed.
//
// The Watchlist tab is a tab, and an operator working FT8 lives on the decodes
// one. A station they asked to be told about would appear on a screen they are
// not looking at — so the same answer is docked in the widget strip, which sits
// above the tabs and is therefore always in view. Only active AND needed: a
// list of everything watched is the tab's job, and it would not fit here.
import { useEffect, useMemo, useState } from 'react';
import { Bell, X } from 'lucide-react';
import { useI18n } from '@/lib/i18n';
import { cn } from '@/lib/utils';
import { inferSpotMode, spotStatusKey } from '@/lib/spot';
import { useWatchlistSpots, newBadge, type WLEntry } from '@/lib/watchlistSpots';
import type { ClusterSpot, SpotStatusEntry } from '@/components/ClusterGrid';
import { WatchlistEntries } from '../../wailsjs/go/main/App';
import { EventsOn } from '../../wailsjs/runtime/runtime';

interface Props {
  spots: ClusterSpot[];
  // The cluster's own verdicts. WHAT a station is worth is the reason to leave
  // what you are doing for it — "on the air and not worked" says nothing about
  // whether it is a new entity or a fifth band on one worked in 1998.
  spotStatus: Record<string, SpotStatusEntry>;
  // A row is a spot: clicking it does what clicking one in the cluster does.
  onPick?: (s: ClusterSpot) => void;
  onClose?: () => void;
}

function age(s: ClusterSpot): string {
  const ts = Date.parse(String((s as any).received_at ?? ''));
  if (!(ts > 0)) return '';
  const m = Math.floor((Date.now() - ts) / 60000);
  if (m < 1) return 'now';
  return `${m}m`;
}

export function WatchlistWidget({ spots, spotStatus, onPick, onClose }: Props) {
  const { t } = useI18n();
  const [entries, setEntries] = useState<WLEntry[]>([]);

  const load = () => { WatchlistEntries().then((e: any) => setEntries((e ?? []) as WLEntry[])).catch(() => {}); };
  useEffect(() => {
    load();
    // The list changes from four places (the tab, the cluster's star, the
    // contest auto-add, an import), and a widget that only reads it at mount
    // would quietly watch the wrong set for the rest of the session.
    const off = EventsOn('watchlist:changed', load);
    return () => { off?.(); };
  }, []);

  const { spotsFor, workedFor, settled } = useWatchlistSpots(entries, spots);

  // Ticking, because every row carries an age and the freshest thing here is
  // the reason to look at it at all.
  const [, tick] = useState(0);
  useEffect(() => {
    const id = window.setInterval(() => tick((n) => n + 1), 30000);
    return () => window.clearInterval(id);
  }, []);

  // One row per (entry, needed slot): the same station on two bands is two
  // chances to work it, and collapsing them would hide the one that is open.
  const rows = useMemo(() => {
    const out: { e: WLEntry; s: ClusterSpot }[] = [];
    for (const e of entries) {
      for (const s of spotsFor.get(e.callsign) ?? []) {
        if (!settled(e, s) || workedFor(e, s)) continue;
        out.push({ e, s });
      }
    }
    // Freshest first: a spot from twenty minutes ago is history, and this
    // panel is short.
    return out.sort((a, b) =>
      Date.parse(String((b.s as any).received_at ?? '')) - Date.parse(String((a.s as any).received_at ?? '')));
  }, [entries, spotsFor, workedFor, settled]);

  return (
    <section className="flex flex-col h-full min-h-0 rounded-lg border border-border bg-card overflow-hidden">
      <div className="flex items-center gap-2 px-3 py-1.5 bg-muted/40 border-b border-border shrink-0">
        <Bell className="size-4 text-primary shrink-0" />
        <span className="text-xs font-semibold uppercase tracking-wider text-muted-foreground">
          {t('wlw.title')}
        </span>
        <span className={cn('rounded px-1.5 py-px text-[10px] font-bold',
          rows.length > 0 ? 'bg-warning text-warning-foreground' : 'bg-muted text-muted-foreground')}>
          {rows.length}
        </span>
        <div className="flex-1" />
        {onClose && (
          <button type="button" onClick={onClose} title={t('wlw.hide')}
            className="text-muted-foreground hover:text-foreground transition-colors">
            <X className="size-3.5" />
          </button>
        )}
      </div>

      <div className="flex-1 min-h-0 overflow-y-auto">
        {rows.length === 0 ? (
          // Empty is the normal state, and it means something precise — say it,
          // rather than leaving a blank box that reads as broken.
          <div className="px-3 py-3 text-xs text-muted-foreground">
            {entries.length === 0 ? t('wlw.emptyList') : t('wlw.emptyNone')}
          </div>
        ) : rows.map(({ e, s }, i) => {
          const mode = inferSpotMode(s.comment ?? '', s.freq_hz) || '';
          const badge = newBadge(spotStatus[spotStatusKey(s.dx_call, s.band ?? '', s.comment ?? '', s.freq_hz)]?.status);
          return (
            <button
              key={`${e.callsign}-${s.dx_call}-${s.band}-${mode}-${i}`}
              type="button"
              onClick={() => onPick?.(s)}
              title={e.callsign === s.dx_call
                ? t('wlw.pick', { call: s.dx_call })
                : `${t('wlw.pick', { call: s.dx_call })} — ${e.callsign}`}
              className="w-full text-left px-2 py-1 border-b border-border/20 hover:bg-muted/50 transition-colors flex items-center gap-1.5"
            >
              {/* One line, in reading order: who, where, what it is worth, how
                  long ago. The callsign is the only part allowed to give way —
                  everything else is short and fixed, and a row that wraps is a
                  row an operator has to parse instead of scan. */}
              <span className="font-mono text-[13px] font-bold text-warning truncate">{s.dx_call}</span>
              {s.band && (
                <span className="rounded px-1 py-px text-[10px] font-bold uppercase bg-info-muted text-info-muted-foreground shrink-0">
                  {s.band}
                </span>
              )}
              {mode && <span className="font-mono text-[10px] text-muted-foreground shrink-0">{mode}</span>}
              <div className="flex-1" />
              {/* Why it is worth interrupting a QSO for — or not. */}
              {badge && (
                <span className="rounded px-1 py-px text-[10px] font-bold uppercase tracking-wide border shrink-0"
                  style={{ color: badge.colour, borderColor: `color-mix(in srgb, ${badge.colour} 45%, transparent)`,
                    background: `color-mix(in srgb, ${badge.colour} 12%, transparent)` }}>
                  {t(badge.key)}
                </span>
              )}
              <span className="font-mono text-[10px] text-muted-foreground/70 tabular-nums shrink-0 w-7 text-right">
                {age(s)}
              </span>
            </button>
          );
        })}
      </div>
    </section>
  );
}
