import { useEffect, useMemo, useState } from 'react';
import { X, Plus, Loader2 } from 'lucide-react';
import { SearchAwardReferences, GetAwardDefs, GetAwardReferenceMeta, AwardRefsNew, GetTrackedAwards } from '../../wailsjs/go/main/App';
import {
  Select, SelectTrigger, SelectValue, SelectContent, SelectItem,
} from '@/components/ui/select';
import { useI18n } from '@/lib/i18n';

type AwardRef = { code: string; name: string; dxcc: number; group: string; subgrp: string };
type AwardDef = { code: string; name: string; field?: string; dxcc_filter?: number[] | null; dynamic?: boolean; valid?: boolean };
type Meta = { code: string; count: number; can_update: boolean };

// Fields auto-derived from structured QSO data — their awards (DXCC/WAZ/WAS/…)
// are computed, never manually picked, so they don't belong in this picker.
// Fields purely derived from the callsign / cty.dat — their awards are computed,
// never picked. NB: 'state' and 'cnty' are NOT here — they're operator-settable
// QSO fields driving predefined-list awards (WAS/RAC/WAJA/JCC), so they ARE
// pickable (a lookup rarely fills the JA prefecture or VE province).
const COMPUTED_FIELDS = new Set(['dxcc', 'cqz', 'ituz', 'prefix', 'callsign', 'cont', 'country', 'grid', 'grid4']);

// If DXCC-filtered auto-results exceed this, require the user to type instead.
const AUTO_SHOW_MAX = 100;

interface Props {
  dxcc?: number;
  // Semicolon-delimited "AWARD@REF" entries, e.g. "POTA@FR-11553;IOTA@EU-064"
  value: string;
  onChange: (v: string) => void;
  // Current QSO field values (state, cnty, …). When a predefined award reads one
  // of these and it's already filled (e.g. VE9CF → state NB), the award counts
  // automatically — we surface that so the operator needn't pick it by hand.
  fieldValues?: Record<string, string>;
  // Height of the selector. Default is a fixed 210px (the QSO editor modal);
  // the live entry panel passes "flex-1 min-h-0" so it fills the available
  // space instead of overflowing and forcing a scrollbar.
  heightClass?: string;
}

export function AwardRefSelector({ dxcc, value, onChange, fieldValues, heightClass = 'h-[210px]' }: Props) {
  const { t } = useI18n();
  const [defs, setDefs] = useState<AwardDef[]>([]);
  const [metas, setMetas] = useState<Record<string, Meta>>({});
  const [awardCode, setAwardCode] = useState('POTA');

  const [q, setQ] = useState('');
  // autoResults: loaded immediately when award/dxcc changes (empty query, DXCC-filtered).
  // Shown when q is short and count ≤ AUTO_SHOW_MAX (e.g. 5 IOTA refs for France).
  const [autoResults, setAutoResults] = useState<AwardRef[]>([]);
  // searchResults: loaded when user types 2+ chars.
  const [searchResults, setSearchResults] = useState<AwardRef[]>([]);
  const [busy, setBusy] = useState(false);
  const [selectedRef, setSelectedRef] = useState<AwardRef | null>(null);
  const [selectedEntry, setSelectedEntry] = useState<string | null>(null);

  const entries = value ? value.split(';').filter(Boolean) : [];

  // Which of the picked references have never been worked. Asked of the backend
  // only when the list of references actually changes — a handful of times per
  // QSO — and it answers from an in-memory set (see awardnew.go).
  //
  // The first question about an award may arrive before its set is built, in
  // which case the answer is "pending" and carries no verdict for it: we ask
  // again rather than badge on a guess. Attempts are capped so a logbook that
  // never finishes building cannot leave a timer running for the session.
  const [isNew, setIsNew] = useState<Record<string, boolean>>({});
  const entriesKey = entries.join(';');
  useEffect(() => {
    if (!entriesKey) { setIsNew({}); return; }
    let alive = true;
    let timer: number | undefined;
    let tries = 0;
    const ask = () => {
      AwardRefsNew(entriesKey.split(';'))
        .then((r: any) => {
          if (!alive) return;
          setIsNew(r?.new ?? {});
          if (r?.pending && ++tries < 10) timer = window.setTimeout(ask, 700);
        })
        .catch(() => { if (alive) setIsNew({}); }); // no badge beats a wrong one
    };
    ask();
    return () => { alive = false; if (timer) window.clearTimeout(timer); };
  }, [entriesKey]);

  // When the reference set is cleared (a QSO was logged or the call was reset),
  // also blank the picker's own state — the search box and the highlighted /
  // selected reference — so the Awards tab starts fresh for the next contact.
  useEffect(() => {
    if (!value) {
      setSelectedEntry(null);
      setSelectedRef(null);
      setQ('');
      setSearchResults([]);
    }
  }, [value]);

  // The awards the operator FOLLOWS, exactly as the Awards tab reads them.
  // This panel used to offer every award that existed, so a station chasing
  // three of them was picking references out of a list of twenty.
  const [tracked, setTracked] = useState<Set<string>>(new Set());
  useEffect(() => {
    Promise.all([GetAwardDefs(), GetAwardReferenceMeta(), GetTrackedAwards()])
      .then(([d, m, tr]) => {
        setDefs((d ?? []) as any);
        setMetas(Object.fromEntries(((m ?? []) as Meta[]).map((x) => [String(x.code).toUpperCase(), x])));
        setTracked(new Set(((tr ?? []) as string[]).map((c) => String(c).toUpperCase())));
      })
      .catch(() => {});
  }, []);

  // An award is offered when its DXCC scope matches the contacted entity (or it
  // has no scope) AND it has references to pick from (a loaded list, an online
  // list, or dynamic references like POTA). This is why DDFM (scope 227) shows
  // for a French call but not for others.
  const awards = useMemo(() => {
    return defs.filter((d) => {
      // Computed awards (field = dxcc/cqz/…) are derived automatically.
      if (COMPUTED_FIELDS.has(String(d.field ?? '').toLowerCase())) return false;
      // Switched off in the award editor: not a candidate for anything.
      if (d.valid === false) return false;
      // Followed awards only, when a selection has been made. An empty
      // selection means "all of them" — the same rule the Awards tab uses, so
      // the two lists cannot disagree about what this station chases.
      if (tracked.size > 0 && !tracked.has(String(d.code).toUpperCase())) return false;
      const scope = d.dxcc_filter ?? [];
      if (scope.length > 0 && (!dxcc || !scope.includes(dxcc))) return false;
      // Offer the award even when its reference list isn't loaded yet: a custom
      // award (e.g. "Worked All Provinces of China") has no built-in list, so
      // requiring one would make it impossible to assign references by hand. The
      // operator can pick from a loaded list when present, or add an unlisted
      // reference (the right-hand panel). Dynamic / list-backed awards behave as
      // before — they just always pass here now.
      return true;
    }).map((d) => ({ code: d.code, name: d.name, field: String(d.field ?? '').toLowerCase() }))
      .sort((a, b) => a.code.localeCompare(b.code));
  }, [defs, metas, dxcc, tracked]);

  // Keep the selected award valid as the offered list changes with the call.
  useEffect(() => {
    if (awards.length === 0) return;
    if (!awards.find((a) => a.code === awardCode)) setAwardCode(awards[0].code);
  }, [awards, awardCode]);

  // Is the selected award a giant dynamic list (POTA/SOTA/IOTA)? Those carry a
  // per-reference DXCC so we filter by entity; predefined lists (WAS/RAC/WAJA)
  // are small and their refs may lack a per-ref DXCC, so we load them whole.
  const isDynamic = useMemo(
    () => !!defs.find((d) => d.code === awardCode)?.dynamic,
    [defs, awardCode],
  );
  // For dynamic lists, restrict to the contacted entity; otherwise load all.
  const refDxcc = isDynamic ? (dxcc ?? 0) : 0;

  // The field the selected award reads (state / cnty / note / …).
  const selField = useMemo(
    () => String(defs.find((d) => d.code === awardCode)?.field ?? '').toLowerCase(),
    [defs, awardCode],
  );

  // Search helper with a DXCC fallback: try the entity-scoped query first, but
  // if it finds nothing AND we were filtering by DXCC, retry unfiltered. This
  // fixes awards whose references carry no per-ref DXCC (e.g. SOTA summits,
  // where the country is in the summit prefix F/AB-001, not a DXCC column) —
  // otherwise filtering by entity returns zero. POTA/IOTA keep entity filtering
  // when their refs do match.
  const searchRefs = async (query: string, limit: number): Promise<AwardRef[]> => {
    let r = (await SearchAwardReferences(awardCode, query, refDxcc, limit)) as any as AwardRef[];
    if ((!r || r.length === 0) && refDxcc > 0) {
      r = (await SearchAwardReferences(awardCode, query, 0, limit)) as any as AwardRef[];
    }
    return r ?? [];
  };

  // Auto-load refs on award/dxcc change with empty query. Fetches AUTO_SHOW_MAX+1
  // so we can distinguish "all results shown" from "too many to list".
  useEffect(() => {
    setAutoResults([]);
    // Dynamic lists need an entity to scope to; predefined lists load regardless.
    if (isDynamic && !dxcc) return;
    searchRefs('', AUTO_SHOW_MAX + 1)
      .then((r) => setAutoResults(r))
      .catch(() => {});
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [awardCode, dxcc, isDynamic, refDxcc]);

  // Typed search (2+ chars).
  useEffect(() => {
    if (q.length < 2) { setSearchResults([]); return; }
    const t = window.setTimeout(async () => {
      setBusy(true);
      try {
        setSearchResults(await searchRefs(q, 50));
      } catch { setSearchResults([]); }
      finally { setBusy(false); }
    }, 200);
    return () => window.clearTimeout(t);
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [awardCode, q, refDxcc]);

  const tooManyAuto = autoResults.length > AUTO_SHOW_MAX;
  // When q is empty/short: show auto-results (if ≤ AUTO_SHOW_MAX); when typing: show search results.
  const results: AwardRef[] = q.length >= 2 ? searchResults : (tooManyAuto ? [] : autoResults);

  // Auto-match: when the selected award reads a QSO field that's already filled
  // (e.g. RAC reads `state`, and the call resolved to state NB), find the matching
  // reference so the operator can add it in one click — the award already counts
  // it from the field, but this makes it explicit and confirms it.
  const autoMatch = useMemo(() => {
    if (!selField || !fieldValues) return null;
    const v = String(fieldValues[selField] ?? '').trim().toUpperCase();
    if (!v) return null;
    const pool = (autoResults.length ? autoResults : searchResults);
    const m = pool.find((r) => r.code.toUpperCase() === v);
    return m ? { code: m.code, name: m.name } : null;
  }, [selField, fieldValues, autoResults, searchResults]);
  const autoAlreadyAdded = autoMatch ? entries.includes(`${awardCode}@${autoMatch.code}`) : false;

  function addRef(ref: AwardRef) {
    const entry = `${awardCode}@${ref.code}`;
    if (!entries.includes(entry)) {
      onChange([...entries, entry].join(';'));
    }
  }

  function removeEntry(entry: string) {
    const next = entries.filter((e) => e !== entry).join(';');
    onChange(next);
    if (selectedEntry === entry) setSelectedEntry(null);
  }

  return (
    <div className={`flex gap-2 ${heightClass}`}>
      {/* Left panel */}
      <div className="flex flex-col gap-1.5 flex-1 min-w-0">

        <Select
          value={awardCode}
          onValueChange={(v) => { setAwardCode(v); setSelectedRef(null); setQ(''); setSearchResults([]); }}
        >
          <SelectTrigger className="h-7 text-xs">
            <SelectValue />
          </SelectTrigger>
          <SelectContent>
            {awards.map((a) => (
              <SelectItem key={a.code} value={a.code}>{a.code}</SelectItem>
            ))}
          </SelectContent>
        </Select>

        {/* Group / Sub from selected ref */}
        <div className="grid grid-cols-[38px_1fr] items-center gap-x-1.5 gap-y-0.5 text-xs">
          <span className="text-muted-foreground text-[11px]">{t('awrs.group')}</span>
          <span className="font-mono truncate text-[11px]">{selectedRef?.group || '—'}</span>
          <span className="text-muted-foreground text-[11px]">{t('awrs.sub')}</span>
          <span className="font-mono truncate text-[11px]">{selectedRef?.subgrp || '—'}</span>
        </div>

        {/* Selected ref chip (nothing shown until one is picked) */}
        {selectedRef && (
          <div className="flex items-center gap-1.5 h-6 px-2 rounded border border-success-border bg-success-muted text-success-muted-foreground text-xs min-w-0">
            <span className="font-mono font-semibold shrink-0">{selectedRef.code}</span>
            <span className="truncate text-[10px] text-success-muted-foreground">{selectedRef.name}</span>
            <button className="ml-auto shrink-0 hover:text-success" onClick={() => setSelectedRef(null)}>
              <X className="size-3" />
            </button>
          </div>
        )}

        {/* Add — references are always scoped to the contacted DXCC */}
        <div className="flex items-center gap-2">
          <button
            disabled={!selectedRef}
            onClick={() => selectedRef && addRef(selectedRef)}
            className="flex items-center gap-1 h-6 px-2 text-xs rounded border border-border hover:bg-accent disabled:opacity-40 disabled:cursor-not-allowed"
          >
            <Plus className="size-3" />{t('awrs.add')}
          </button>
          <span className="text-[11px] text-muted-foreground">
            {dxcc ? `DXCC #${dxcc}` : t('awrs.enterCallsignFirst')}
          </span>
        </div>

        <div className="h-px bg-border shrink-0" />

        {/* Added refs list */}
        <div className="flex-1 overflow-auto space-y-0.5 min-h-0">
          {entries.length === 0 ? (
            <p className="text-[11px] text-muted-foreground italic py-0.5">{t('awrs.noRefsAdded')}</p>
          ) : (
            entries.map((entry) => (
              <div
                key={entry}
                className={`flex items-center gap-1.5 px-2 py-0.5 rounded text-xs cursor-pointer select-none ${
                  selectedEntry === entry ? 'bg-accent' : 'hover:bg-accent/50'
                }`}
                onClick={() => setSelectedEntry(selectedEntry === entry ? null : entry)}
              >
                <span className="font-mono font-semibold truncate">{entry}</span>
                {isNew[entry] && (
                  <span
                    title={t('awrs.newRefHint')}
                    className="shrink-0 px-1 rounded-sm text-[9px] leading-[14px] font-semibold tracking-wide bg-success-muted text-success border border-success-border"
                  >
                    {t('awrs.new')}
                  </span>
                )}
                <span className="flex-1" />
                <button
                  className="shrink-0 hover:text-destructive"
                  onClick={(e) => { e.stopPropagation(); removeEntry(entry); }}
                >
                  <X className="size-3" />
                </button>
              </div>
            ))
          )}
        </div>
      </div>

      {/* Right panel: reference search */}
      <div className="w-[172px] shrink-0 flex flex-col gap-1.5 border-l pl-2 min-w-0">
        <span className="text-xs font-semibold">{t('awrs.references')}</span>
        {/* Auto-match from the QSO field (e.g. State NB → RAC@NB). */}
        {autoMatch && (
          <button
            type="button"
            disabled={autoAlreadyAdded}
            onClick={() => addRef({ code: autoMatch.code, name: autoMatch.name } as AwardRef)}
            className={`text-left rounded border px-1.5 py-1 text-[11px] leading-tight ${
              autoAlreadyAdded
                ? 'border-success-border bg-success-muted text-success-muted-foreground cursor-default'
                : 'border-success-border bg-success-muted/60 text-success-muted-foreground hover:bg-success-muted'
            }`}
            title={t('awrs.autoMatchTitle', { field: selField.toUpperCase(), code: autoMatch.code })}
          >
            {autoAlreadyAdded ? '✓ ' : '+ '}
            <span className="font-mono font-semibold">{autoMatch.code}</span>
            <span className="text-success-muted-foreground"> {t('awrs.fromField', { field: selField })}</span>
            {!autoAlreadyAdded && <span className="block text-[10px] text-success-muted-foreground/80">{t('awrs.autoClickToAdd')}</span>}
          </button>
        )}
        <input
          className="h-6 w-full rounded border border-input bg-background px-2 text-xs focus:outline-none focus:ring-1 focus:ring-ring"
          placeholder={t('awrs.search')}
          value={q}
          onChange={(e) => setQ(e.target.value)}
        />
        {/* Add an UNLISTED reference: type a new code (e.g. a brand-new POTA park
            not yet in the list) and add it directly. */}
        {q.trim().length >= 2 && !results.some((r) => r.code.toUpperCase() === q.trim().toUpperCase()) && (
          <button
            type="button"
            onClick={() => { addRef({ code: q.trim().toUpperCase(), name: '' } as AwardRef); setQ(''); }}
            className="text-left rounded border border-dashed border-primary/50 px-1.5 py-1 text-[11px] text-primary hover:bg-primary/10"
            title={t('awrs.addUnlistedTitle')}
          >
            {t('awrs.addPrefix')} <span className="font-mono font-semibold">{q.trim().toUpperCase()}</span>
            <span className="text-[10px] text-muted-foreground"> {t('awrs.unlisted')}</span>
          </button>
        )}
        <div className="flex-1 overflow-auto border rounded-md text-xs min-h-0">
          {busy && (
            <div className="flex items-center gap-1.5 px-2 py-1.5 text-muted-foreground">
              <Loader2 className="size-3 animate-spin" />{t('awrs.searching')}
            </div>
          )}
          {/* Too many auto-results → require typed search */}
          {!busy && q.length < 2 && tooManyAuto && (
            <div className="px-2 py-1.5 text-[11px] text-muted-foreground leading-snug">
              {t('awrs.typeToSearch')}
            </div>
          )}
          {/* Empty short-query state: prompt for a callsign (dynamic lists) or
              note the list is empty (predefined awards with no references). */}
          {!busy && q.length < 2 && !tooManyAuto && autoResults.length === 0 && (
            <div className="px-2 py-1.5 text-[11px] text-muted-foreground leading-snug">
              {isDynamic && !dxcc
                ? t('awrs.enterCallsignOrSearch')
                : t('awrs.noRefsForEntity')}
            </div>
          )}
          {/* Typed search, no results */}
          {!busy && q.length >= 2 && results.length === 0 && (
            <div className="px-2 py-2 text-[11px] text-muted-foreground leading-snug">
              {t('awrs.noResults')}
              <br />
              <span className="text-[10px]">{t('awrs.downloadLists')}</span>
            </div>
          )}
          {results.map((r) => (
            <div
              key={r.code}
              className={`px-2 py-1 cursor-pointer border-b border-border/30 last:border-0 ${
                selectedRef?.code === r.code ? 'bg-accent' : 'hover:bg-accent/50'
              }`}
              onClick={() => setSelectedRef(r)}
              onDoubleClick={() => { setSelectedRef(r); addRef(r); }}
            >
              <div className="font-mono font-semibold leading-tight text-[11px]">{r.code}</div>
              {r.name && (
                <div className="text-[10px] text-muted-foreground leading-tight truncate">{r.name}</div>
              )}
            </div>
          ))}
        </div>
      </div>
    </div>
  );
}
