import { useEffect, useMemo, useState } from 'react';
import { ComputeQSOAwardRefs } from '../../wailsjs/go/main/App';
import { Input } from '@/components/ui/input';
import { Label } from '@/components/ui/label';
import { Checkbox } from '@/components/ui/checkbox';
import {
  Select, SelectTrigger, SelectValue, SelectContent, SelectItem,
} from '@/components/ui/select';
import { cn } from '@/lib/utils';
import { useI18n } from '@/lib/i18n';
import { pathBetween, pathBetweenLatLon, gridToLatLon } from '@/lib/maidenhead';
import { BandSlotGrid } from '@/components/BandSlotGrid';
import { AwardRefSelector } from '@/components/AwardRefSelector';

export interface DetailsState {
  state: string;
  cnty: string;
  address: string;
  lat?: number;
  lon?: number;
  // DXCC entity number + zones (filled from QRZ/HamQTH or cty.dat fallback).
  // Editable so the operator can correct an obviously wrong auto-fill.
  dxcc?: number;
  cqz?: number;
  ituz?: number;
  cont: string;
  qsl_msg: string;
  qsl_via: string;
  ant_az?: number;
  ant_el?: number;
  ant_path: string;
  prop_mode: string;
  my_rig: string;
  my_antenna: string;
  tx_pwr?: number;
  sat_name: string;
  sat_mode: string;
  contest_id: string;
  // Contest exchanges as free text — most are serials (001) but some are
  // alphanumeric (e.g. a section/zone like "ON4"). Stored to ADIF SRX/STX when
  // purely numeric, else to SRX_STRING/STX_STRING (handled in App on save).
  srx_string?: string;
  stx_string?: string;
  email: string;
  // Award references for the contacted station (set via the Awards tab picker).
  // Semicolon-delimited "AWARD@REF" entries, e.g. "POTA@FR-11553;IOTA@EU-064".
  // App.tsx maps these back to pota_ref/sota_ref/iota when saving the QSO.
  award_refs?: string;
}

interface Props {
  callsign: string;
  prefix: string;
  operatorGrid: string;   // station.my_grid — origin for bearing/distance
  remoteGrid: string;     // entry-strip Grid value — destination
  // Entry-strip text fields. Live award detection matches on these too — a DARC
  // DOK keys off QTH, not the address — and sending only the address meant such
  // an award stayed silent until the QSO was logged.
  qth?: string;
  name?: string;
  country?: string;
  comment?: string;
  note?: string;
  details: DetailsState;
  onChange: (patch: Partial<DetailsState>) => void;
  // Stats (F1) tab content: the worked-before matrix + optional QRZ image.
  wb?: any;
  wbBusy?: boolean;
  band: string;
  mode: string;
  bands?: string[]; // configured bands for the worked-before matrix columns
  imageUrl?: string;
  onOpenImage?: () => void;
  // Optional controlled active tab (so the app can switch it via keyboard).
  tab?: TabName;
  onTab?: (t: TabName) => void;
  // When the WinKeyer is active, F1-F12 fire macros, so the tab shortcut is
  // shown as Ctrl+F1…F5 instead of F1…F5.
  keyerActive?: boolean;
}

export type TabName = 'stats' | 'info' | 'awards' | 'my' | 'extended';

// ADIF PROP_MODE: stored value is the code, shown with the full name (Log4OM-style).
const PROP_MODES: { value: string; label: string }[] = [
  { value: 'NONE', label: '—' },
  { value: 'AS', label: 'detp.propAS' },
  { value: 'AUR', label: 'detp.propAUR' },
  { value: 'AUE', label: 'detp.propAUE' },
  { value: 'BS', label: 'detp.propBS' },
  { value: 'ECH', label: 'EchoLink' },
  { value: 'EME', label: 'detp.propEME' },
  { value: 'ES', label: 'detp.propES' },
  { value: 'FAI', label: 'detp.propFAI' },
  { value: 'F2', label: 'detp.propF2' },
  { value: 'GWAVE', label: 'detp.propGWAVE' },
  { value: 'INTERNET', label: 'detp.propINTERNET' },
  { value: 'ION', label: 'detp.propION' },
  { value: 'IRL', label: 'IRLP' },
  { value: 'LOS', label: 'detp.propLOS' },
  { value: 'MS', label: 'detp.propMS' },
  { value: 'RPT', label: 'detp.propRPT' },
  { value: 'RS', label: 'detp.propRS' },
  { value: 'SAT', label: 'detp.propSAT' },
  { value: 'TEP', label: 'detp.propTEP' },
  { value: 'TR', label: 'detp.propTR' },
];

// ADIF ANT_PATH enum (Grayline, Other, Short Path, Long Path).
const ANT_PATHS: { value: string; label: string }[] = [
  { value: 'NONE', label: '—' },
  { value: 'S', label: 'detp.pathShort' },
  { value: 'L', label: 'detp.pathLong' },
  { value: 'G', label: 'detp.pathGrayline' },
  { value: 'O', label: 'detp.pathOther' },
];

function numOrUndef(v: string): number | undefined {
  if (v === '') return undefined;
  const n = parseFloat(v);
  return isNaN(n) ? undefined : n;
}

// Compact field helper to keep the JSX dense.
// Written out rather than built as `col-span-${n}`: Tailwind scans for literal
// class names, so an interpolated one is never emitted.
const SPAN_CLASS: Record<number, string> = {
  1: '', 2: 'col-span-2', 3: 'col-span-3', 4: 'col-span-4', 5: 'col-span-5',
  6: 'col-span-6', 7: 'col-span-7', 8: 'col-span-8', 9: 'col-span-9',
  10: 'col-span-10', 11: 'col-span-11', 12: 'col-span-12',
};

function Field({ label, span = 1, className, children }: { label: string; span?: number; className?: string; children: React.ReactNode }) {
  return (
    <div className={cn('flex flex-col min-w-0', SPAN_CLASS[span], className)}>
      <Label className="mb-1">{label}</Label>
      {children}
    </div>
  );
}

export function DetailsPanel({ callsign, prefix, operatorGrid, remoteGrid, qth, name, country, comment, note, details, onChange, wb, wbBusy, band, mode, bands, tab, onTab, keyerActive }: Props) {
  const { t } = useI18n();
  const [internalOpen, setInternalOpen] = useState<TabName>('stats');
  const open = tab ?? internalOpen; // controlled when `tab` is provided

  // Live award detection: run the SAME engine used at log time over the current
  // contact — callsign, the looked-up address/state/zones AND the entry-strip
  // text (QTH, name, country, comment, note, grid). Sending only the address was
  // a silent hole: a DARC DOK matches on QTH, so "Aachen" produced nothing here
  // while the award editor's Test tab, which passes the whole QSO, matched it.
  // References the QSO will earn — WAPC matching "Beijing" inside the address,
  // a DOK matching the town — are surfaced and auto-added the moment you enter a
  // call or click a spot, instead of only appearing after logging. Pickable
  // matches are merged into award_refs
  // (idempotent; award_refs is NOT a dependency, so removing one by hand sticks).
  const [detected, setDetected] = useState<Array<{ code: string; ref: string; name?: string; pickable?: boolean; ambiguous?: boolean }>>([]);
  useEffect(() => {
    if (open !== 'awards' || !callsign.trim()) { setDetected([]); return; }
    const t = window.setTimeout(async () => {
      try {
        const q: any = {
          callsign, band, mode,
          address: details.address ?? '', state: details.state ?? '', cnty: details.cnty ?? '',
          qth: qth ?? '', name: name ?? '', country: country ?? '',
          comment: comment ?? '', notes: note ?? '', grid: remoteGrid ?? '',
          cont: details.cont ?? '', dxcc: details.dxcc, cqz: details.cqz, ituz: details.ituz,
          qso_date: new Date().toISOString(),
        };
        const all = ((await ComputeQSOAwardRefs(q)) ?? []) as any[];
        setDetected(all as any);
        const cur = details.award_refs ?? '';
        const have = new Set(cur.split(';').filter(Boolean));
        let next = cur;
        for (const r of all) {
          // An ambiguous candidate is offered, never auto-added: adding one would
          // be the guess the award's "one reference per QSO" setting exists to refuse.
          if (!r.pickable || r.ambiguous) continue;
          const entry = `${String(r.code).toUpperCase()}@${String(r.ref).toUpperCase()}`;
          if (!have.has(entry)) { next = next ? `${next};${entry}` : entry; have.add(entry); }
        }
        if (next !== cur) onChange({ award_refs: next });
      } catch { /* leave detection empty on failure */ }
    }, 400);
    return () => window.clearTimeout(t);
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [open, callsign, details.address, details.state, details.cnty, details.dxcc, details.cqz, details.ituz, qth, name, country, comment, note, remoteGrid, band, mode]);
  // Bearing/distance from operator's home grid to the remote station.
  // Recomputed only when either grid actually changes.
  const path = useMemo(() => {
    const byGrid = pathBetween(operatorGrid, remoteGrid);
    if (byGrid) return byGrid;
    // Fall back to lat/lon when the DX has coordinates but no grid (e.g. a
    // cty.dat-only entity like Svalbard: no QRZ grid, but cty.dat coordinates).
    const myLL = gridToLatLon(operatorGrid);
    if (myLL && details.lat != null && details.lon != null) {
      return pathBetweenLatLon(myLL, { lat: details.lat, lon: details.lon });
    }
    return null;
  }, [operatorGrid, remoteGrid, details.lat, details.lon]);
  const fmtDeg = (n: number) => `${Math.round(n)}°`;
  const fmtKm = (n: number) => `${Math.round(n).toLocaleString()} km`;

  function toggle(t: TabName) { onTab ? onTab(t) : setInternalOpen(t); }
  const fk = keyerActive ? 'Ctrl+F' : 'F';

  const satelliteMode = !!details.sat_name || !!details.sat_mode || details.prop_mode === 'SAT';
  function setSatellite(on: boolean) {
    if (on) {
      if (details.prop_mode !== 'SAT') onChange({ prop_mode: 'SAT' });
    } else {
      onChange({
        sat_name: '', sat_mode: '',
        ...(details.prop_mode === 'SAT' ? { prop_mode: '' } : {}),
      });
    }
  }

  const tabs: { key: TabName; label: string }[] = [
    { key: 'stats',    label: `${t('detp.tabStats')} (${fk}1)` },
    { key: 'info',     label: `${t('detp.tabInfo')} (${fk}2)` },
    { key: 'awards',   label: `${t('detp.tabAwards')} (${fk}3)` },
    { key: 'my',       label: `${t('detp.tabMy')} (${fk}4)` },
    { key: 'extended', label: `${t('detp.tabExtended')} (${fk}5)` },
  ];

  return (
    <section className="bg-card shadow-sm border border-border rounded-lg flex flex-col flex-1 min-h-0 overflow-hidden">
      <nav className="flex items-center gap-1 px-3 bg-muted/40 border-b border-border shrink-0">
        {tabs.map((t) => (
          <button
            key={t.key}
            onClick={() => toggle(t.key)}
            className={cn(
              'px-3 py-1.5 text-xs font-medium border-b-2 border-transparent -mb-px transition-colors',
              open === t.key
                ? 'text-primary border-primary font-semibold'
                : 'text-muted-foreground hover:text-foreground',
            )}
          >
            {t.label}
          </button>
        ))}
      </nav>

      <div className={cn('flex-1 min-h-0', open === 'stats' ? 'overflow-hidden' : 'overflow-y-auto')}>
      {open === 'stats' && (
        <div className="px-3 py-2.5">
          <BandSlotGrid wb={wb} busy={!!wbBusy} currentBand={band} currentMode={mode} bands={bands} hasCall={callsign.trim() !== ''} />
        </div>
      )}

      {open === 'info' && (
        <div className="grid grid-cols-12 gap-2 px-3 py-2.5">
          {/* Twelve columns, not six: the fields hold very different things. A US
              county name is long, a CQ zone is two digits — on an even six-column
              grid they were the same width, so the county truncated while the
              zones sat mostly empty. */}
          <Field label={t('detp.statePref')} span={2}>
            <Input value={details.state} onChange={(e) => onChange({ state: e.target.value })} />
          </Field>
          <Field label={t('detp.county')} span={4}>
            <Input value={details.cnty} onChange={(e) => onChange({ cnty: e.target.value })} />
          </Field>
          <Field label={t('detp.prefix')} span={2}>
            <Input className="font-mono uppercase" value={prefix} readOnly tabIndex={-1} />
          </Field>
          <Field label={t('detp.cqZone')}>
            <Input inputMode="numeric" maxLength={2} className="font-mono" value={details.cqz ?? ''} placeholder="—"
              onChange={(e) => { const v = e.target.value.replace(/\D/g, ''); onChange({ cqz: v === '' ? undefined : parseInt(v, 10) }); }} />
          </Field>
          <Field label={t('detp.ituZone')}>
            <Input inputMode="numeric" maxLength={2} className="font-mono" value={details.ituz ?? ''} placeholder="—"
              onChange={(e) => { const v = e.target.value.replace(/\D/g, ''); onChange({ ituz: v === '' ? undefined : parseInt(v, 10) }); }} />
          </Field>
          {/* DXCC # closes the top row (next to the zones); Continent and
              Azimuth SP live in the main entry strip / bandeau. The long-path
              bearing and distances move to the row below. */}
          <Field label={t('detp.dxcc')} span={2}>
            <Input
              readOnly
              tabIndex={-1}
              className="font-mono bg-muted/40 cursor-default"
              value={details.dxcc ?? ''}
              placeholder="—"
            />
          </Field>
          {/* The bearing and the two distances are read-only and hold at most
              "12345 km", so they take a fixed width and the address — the one
              field here that is never wide enough — gets everything left over. */}
          <div className="col-span-12 flex gap-2">
            <Field label={t('detp.azimuthLp')} className="w-20 shrink-0">
              <Input
                readOnly
                tabIndex={-1}
                className="font-mono bg-muted/40 cursor-default"
                value={path ? fmtDeg(path.bearingLong) : ''}
                placeholder="—"
              />
            </Field>
            <Field label={t('detp.distanceSp')} className="w-24 shrink-0">
              <Input
                readOnly
                tabIndex={-1}
                className="font-mono bg-muted/40 cursor-default"
                value={path ? fmtKm(path.distanceShort) : ''}
                placeholder="—"
              />
            </Field>
            <Field label={t('detp.distanceLp')} className="w-24 shrink-0">
              <Input
                readOnly
                tabIndex={-1}
                className="font-mono bg-muted/40 cursor-default"
                value={path ? fmtKm(path.distanceLong) : ''}
                placeholder="—"
              />
            </Field>
            <Field label={t('detp.address')} className="flex-1 min-w-0">
              <Input value={details.address} onChange={(e) => onChange({ address: e.target.value })} />
            </Field>
          </div>
          <Field label={t('detp.qslMessage')} span={7}>
            <Input value={details.qsl_msg} onChange={(e) => onChange({ qsl_msg: e.target.value })} />
          </Field>
          <Field label={t('detp.qslVia')} span={5}>
            <Input value={details.qsl_via} onChange={(e) => onChange({ qsl_via: e.target.value })} />
          </Field>
        </div>
      )}

      {open === 'awards' && (
        <div className="px-3 py-2.5 h-full flex flex-col min-h-0">
          <AwardRefSelector
            dxcc={details.dxcc}
            value={details.award_refs ?? ''}
            onChange={(v) => onChange({ award_refs: v })}
            fieldValues={{ state: details.state ?? '', cnty: details.cnty ?? '' }}
            heightClass="flex-1 min-h-0"
          />
          {/* Ambiguous candidates: the award allows one reference per QSO and
              several matched, so none was kept. Click one to settle it while the
              contact is still in front of you. */}
          {detected.some((r) => r.ambiguous) && (
            <div className="mt-2 text-[11px] shrink-0 leading-snug border-t border-warning/40 pt-1.5">
              <span className="font-medium text-warning">{t('detp.ambiguous')}</span>{' '}
              {detected.filter((r) => r.ambiguous).map((r) => (
                <button
                  key={`amb-${r.code}@${r.ref}`}
                  type="button"
                  title={r.name ?? ''}
                  onClick={() => {
                    const entry = `${r.code.toUpperCase()}@${r.ref.toUpperCase()}`;
                    const cur = details.award_refs ?? '';
                    // Only one of the candidates can be right — replace any
                    // sibling already picked for this award rather than stacking.
                    const kept = cur.split(';').filter((e) => e && e.split('@')[0].toUpperCase() !== r.code.toUpperCase());
                    onChange({ award_refs: [...kept, entry].join(';') });
                    setDetected((d) => d.filter((x) => !x.ambiguous || x.code.toUpperCase() !== r.code.toUpperCase()));
                  }}
                  className="inline-block mr-1.5 px-1.5 py-0.5 rounded font-mono whitespace-nowrap border border-warning/50 text-warning hover:bg-warning/10"
                >
                  {r.code}@{r.ref}{r.name ? ` — ${r.name}` : ''}
                </button>
              ))}
            </div>
          )}
          {detected.some((r) => !r.ambiguous) && (
            <div className="mt-2 text-[11px] text-muted-foreground shrink-0 max-h-14 overflow-y-auto leading-snug border-t border-border/50 pt-1.5">
              <span className="font-medium text-foreground/70">{t('detp.detected')}</span>{' '}
              {detected.filter((r) => !r.ambiguous).map((r) => (
                <span key={`${r.code}@${r.ref}`} className="inline-block mr-1.5 font-mono whitespace-nowrap" title={r.name ?? ''}>
                  <span className="text-foreground/80">{r.code}{r.ref ? `@${r.ref}` : ''}</span>
                </span>
              ))}
            </div>
          )}
        </div>
      )}

      {open === 'my' && (
        <div className="grid grid-cols-6 gap-2 px-3 py-2.5">
          <Field label={t('detp.azimuth')}>
            <Input type="number" value={details.ant_az ?? ''} onChange={(e) => onChange({ ant_az: numOrUndef(e.target.value) })} />
          </Field>
          <Field label={t('detp.elevation')}>
            <Input type="number" value={details.ant_el ?? ''} onChange={(e) => onChange({ ant_el: numOrUndef(e.target.value) })} />
          </Field>
          <Field label={t('detp.txPower')}>
            <Input type="number" value={details.tx_pwr ?? ''} onChange={(e) => onChange({ tx_pwr: numOrUndef(e.target.value) })} />
          </Field>
          <div className="col-span-3 flex items-end pb-1.5">
            <label className="flex items-center gap-2 text-sm cursor-pointer">
              <Checkbox checked={satelliteMode} onCheckedChange={(c) => setSatellite(!!c)} />
              {t('detp.satelliteMode')}
            </label>
          </div>
          <Field label={t('detp.antPath')} span={2}>
            <Select value={details.ant_path || 'NONE'} onValueChange={(v) => onChange({ ant_path: v === 'NONE' ? '' : v })}>
              <SelectTrigger><SelectValue /></SelectTrigger>
              <SelectContent>
                {ANT_PATHS.map((p) => <SelectItem key={p.value} value={p.value}>{t(p.label)}</SelectItem>)}
              </SelectContent>
            </Select>
          </Field>
          <Field label={t('detp.propagation')} span={4}>
            <Select value={details.prop_mode || 'NONE'} onValueChange={(v) => onChange({ prop_mode: v === 'NONE' ? '' : v })}>
              <SelectTrigger><SelectValue /></SelectTrigger>
              <SelectContent>
                {PROP_MODES.map((p) => <SelectItem key={p.value} value={p.value}>{t(p.label)}</SelectItem>)}
              </SelectContent>
            </Select>
          </Field>
          <Field label={t('detp.rig')} span={3}>
            <Input value={details.my_rig} placeholder="Flex 8600" onChange={(e) => onChange({ my_rig: e.target.value })} />
          </Field>
          <Field label={t('detp.antenna')} span={3}>
            <Input value={details.my_antenna} placeholder="UB640" onChange={(e) => onChange({ my_antenna: e.target.value })} />
          </Field>
          {satelliteMode && (
            <>
              <Field label={t('detp.satName')} span={3}>
                <Input value={details.sat_name} placeholder="AO-91" onChange={(e) => onChange({ sat_name: e.target.value })} />
              </Field>
              <Field label={t('detp.satelliteMode')} span={3}>
                <Input value={details.sat_mode} placeholder="U/V" onChange={(e) => onChange({ sat_mode: e.target.value })} />
              </Field>
            </>
          )}
        </div>
      )}

      {open === 'extended' && (
        <div className="grid grid-cols-6 gap-2 px-3 py-2.5">
          <Field label={t('detp.contestId')} span={2}>
            <Input value={details.contest_id} onChange={(e) => onChange({ contest_id: e.target.value })} />
          </Field>
          <Field label="SRX">
            <Input value={details.srx_string ?? ''} placeholder={t('detp.rcvdExchangePh')} onChange={(e) => onChange({ srx_string: e.target.value })} />
          </Field>
          <Field label="STX">
            <Input value={details.stx_string ?? ''} placeholder={t('detp.sentExchangePh')} onChange={(e) => onChange({ stx_string: e.target.value })} />
          </Field>
          <Field label={t('detp.contactedEmail')} span={3}>
            <Input value={details.email} placeholder="op@example.com" onChange={(e) => onChange({ email: e.target.value })} />
          </Field>
        </div>
      )}
      </div>
    </section>
  );
}
