import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import {
  AllCommunityModule, ModuleRegistry,
  type ColDef,
} from 'ag-grid-community';
import { hamlogGridTheme } from '@/lib/gridTheme';
import { AgGridReact } from 'ag-grid-react';
import { Plus, Trash2, Radio, PlusCircle, MinusCircle, Search, UserPlus, History } from 'lucide-react';
import {
  Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter, DialogDescription,
} from '@/components/ui/dialog';
import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
import { Label } from '@/components/ui/label';
import { Badge } from '@/components/ui/badge';
import { QSOEditModal } from '@/components/QSOEditModal';
import { useI18n } from '@/lib/i18n';
import type { QSOForm } from '@/types';
import {
  NetList, NetCreate, NetRename, NetDelete, NetOpen, NetClose, NetOpenID,
  NetRoster, NetRosterUpsert, NetRosterRemove, NetLookup,
  NetActiveList, NetActivate, NetDeactivate, NetUpdateActive, NetDiscardActive,
  WorkedBefore,
} from '@/../wailsjs/go/main/App';
import { netctl } from '@/../wailsjs/go/models';

ModuleRegistry.registerModules([AllCommunityModule]);

const hamlogTheme = hamlogGridTheme;

type Net = netctl.Net;
type Station = netctl.Station;

function fmtTimeOn(s: any): string {
  if (!s) return '';
  const d = new Date(s);
  if (isNaN(d.getTime())) return '';
  const p = (n: number) => String(n).padStart(2, '0');
  return `${p(d.getUTCHours())}:${p(d.getUTCMinutes())}:${p(d.getUTCSeconds())}Z`;
}

const emptyStation = (): Station => netctl.Station.createFrom({ callsign: '' });

type Props = {
  onLogged?: () => void;
  countries?: string[];
  bands?: string[];
  modes?: string[];
};

export function NetControlPanel({ onLogged, countries, bands, modes }: Props) {
  const { t } = useI18n();
  const [nets, setNets] = useState<Net[]>([]);
  const [selId, setSelId] = useState<string>('');
  const [openId, setOpenId] = useState<string>('');
  const [roster, setRoster] = useState<Station[]>([]);
  const [active, setActive] = useState<QSOForm[]>([]);
  const [error, setError] = useState('');

  // Full-QSO edit modal for an on-air draft (same one Recent QSOs uses).
  const [editingDraft, setEditingDraft] = useState<QSOForm | null>(null);

  // Add/edit-contact dialog.
  const [contactOpen, setContactOpen] = useState(false);
  const [contact, setContact] = useState<Station>(emptyStation());
  const [looking, setLooking] = useState(false);

  const activeGrid = useRef<any>(null);
  const rosterGrid = useRef<any>(null);

  // Worked-before for the clicked station (see if/when we contacted it before).
  const [wbCall, setWbCall] = useState('');
  const [wb, setWb] = useState<any>(null);
  const [wbBusy, setWbBusy] = useState(false);
  const wbReq = useRef(0);
  // Resizable height (drag the handle on top). Persisted.
  const [wbHeight, setWbHeight] = useState(() => {
    const v = parseInt(localStorage.getItem('opslog.netWbHeight') || '', 10);
    return v >= 80 && v <= 600 ? v : 176;
  });
  useEffect(() => { localStorage.setItem('opslog.netWbHeight', String(wbHeight)); }, [wbHeight]);
  const startWbResize = (e: React.MouseEvent) => {
    e.preventDefault();
    const startY = e.clientY, startH = wbHeight;
    const onMove = (ev: MouseEvent) => setWbHeight(Math.max(80, Math.min(600, startH - (ev.clientY - startY))));
    const onUp = () => { document.removeEventListener('mousemove', onMove); document.removeEventListener('mouseup', onUp); };
    document.addEventListener('mousemove', onMove);
    document.addEventListener('mouseup', onUp);
  };
  const showWorkedBefore = useCallback(async (call: string, dxcc = 0) => {
    const c = (call ?? '').trim().toUpperCase();
    setWbCall(c);
    if (!c) { setWb(null); return; }
    const req = ++wbReq.current;
    setWbBusy(true);
    try { const r = await WorkedBefore(c, dxcc); if (wbReq.current === req) setWb(r); }
    catch { if (wbReq.current === req) setWb(null); }
    finally { if (wbReq.current === req) setWbBusy(false); }
  }, []);

  const isOpen = openId !== '' && openId === selId;

  const refreshNets = useCallback(async () => {
    try {
      const [list, oid] = await Promise.all([NetList(), NetOpenID()]);
      const arr = (list ?? []) as Net[];
      setNets(arr);
      setOpenId(oid ?? '');
      setSelId((cur) => cur || (oid ?? '') || (arr[0]?.id ?? ''));
    } catch (e: any) { setError(String(e?.message ?? e)); }
  }, []);

  const refreshRoster = useCallback(async (id: string) => {
    if (!id) { setRoster([]); return; }
    try { setRoster(((await NetRoster(id)) ?? []) as Station[]); }
    catch (e: any) { setError(String(e?.message ?? e)); }
  }, []);

  const refreshActive = useCallback(async () => {
    try { setActive(((await NetActiveList()) ?? []) as unknown as QSOForm[]); }
    catch { /* ignore */ }
  }, []);

  useEffect(() => { refreshNets(); }, [refreshNets]);
  useEffect(() => { refreshRoster(selId); }, [selId, refreshRoster]);
  useEffect(() => { if (isOpen) refreshActive(); else setActive([]); }, [isOpen, refreshActive]);

  // The roster side hides callsigns currently on the air (they live in the left
  // grid until logged), mirroring Log4OM's two-list behaviour.
  const activeCalls = useMemo(
    () => new Set(active.map((a) => (a.callsign ?? '').toUpperCase())),
    [active],
  );
  const rosterShown = useMemo(
    () => roster.filter((s) => !activeCalls.has((s.callsign ?? '').toUpperCase())),
    [roster, activeCalls],
  );

  async function newNet() {
    const name = window.prompt(t('ncp.newNetPrompt'));
    if (!name?.trim()) return;
    try { const n = await NetCreate(name.trim()); await refreshNets(); setSelId(n.id); }
    catch (e: any) { setError(String(e?.message ?? e)); }
  }
  async function renameNet() {
    if (!selId) return;
    const cur = nets.find((n) => n.id === selId);
    const name = window.prompt(t('ncp.renamePrompt'), cur?.name ?? '');
    if (!name?.trim()) return;
    try { await NetRename(selId, name.trim()); await refreshNets(); }
    catch (e: any) { setError(String(e?.message ?? e)); }
  }
  async function deleteNet() {
    if (!selId) return;
    const cur = nets.find((n) => n.id === selId);
    if (!window.confirm(t('ncp.deleteConfirm', { name: cur?.name ?? '' }))) return;
    try { await NetDelete(selId); setSelId(''); await refreshNets(); }
    catch (e: any) { setError(String(e?.message ?? e)); }
  }
  async function toggleOpen() {
    try {
      if (isOpen) {
        if (active.length > 0 &&
          !window.confirm(t('ncp.closeConfirm', { n: active.length }))) return;
        await NetClose(); setOpenId('');
      } else {
        await NetOpen(selId); setOpenId(selId); await refreshActive();
      }
    } catch (e: any) { setError(String(e?.message ?? e)); }
  }

  // Roster → active (start QSO).
  async function activate(call: string) {
    if (!isOpen || !call) return;
    try { await NetActivate(call); await refreshActive(); }
    catch (e: any) { setError(String(e?.message ?? e)); }
  }
  // Active → logged (end QSO, removed from session, written to the logbook).
  async function deactivate(id?: number) {
    if (id == null) return;
    try { await NetDeactivate(id); await refreshActive(); onLogged?.(); }
    catch (e: any) { setError(String(e?.message ?? e)); }
  }

  // Edit-modal handlers (operate on the in-memory draft, not the DB).
  async function saveDraft(q: QSOForm) {
    try { await NetUpdateActive(q as any); setEditingDraft(null); await refreshActive(); }
    catch (e: any) { setError(String(e?.message ?? e)); }
  }
  async function discardDraft(id: number) {
    try { await NetDiscardActive(id); setEditingDraft(null); await refreshActive(); }
    catch (e: any) { setError(String(e?.message ?? e)); }
  }

  // Add-contact dialog.
  function openAddContact() { setContact(emptyStation()); setContactOpen(true); }
  async function lookupContact() {
    const call = (contact.callsign ?? '').trim();
    if (!call) return;
    setLooking(true);
    try {
      const r = await NetLookup(call);
      setContact((c) => netctl.Station.createFrom({
        ...c, callsign: (r.callsign || call).toUpperCase(),
        name: r.name || c.name, qth: r.qth || c.qth, country: r.country || c.country,
        dxcc: r.dxcc || c.dxcc, itu: r.itu || c.itu, cq: r.cq || c.cq,
        grid: r.grid || c.grid, address: r.address || c.address, state: r.state || c.state,
        cnty: r.cnty || c.cnty, cont: r.cont || c.cont, lat: r.lat || c.lat, lon: r.lon || c.lon,
        email: r.email || c.email,
      }));
    } catch (e: any) { setError(String(e?.message ?? e)); }
    finally { setLooking(false); }
  }
  async function saveContact() {
    const call = (contact.callsign ?? '').trim().toUpperCase();
    if (!call || !selId) return;
    try {
      await NetRosterUpsert(selId, netctl.Station.createFrom({ ...contact, callsign: call }));
      setContactOpen(false);
      await refreshRoster(selId);
    } catch (e: any) { setError(String(e?.message ?? e)); }
  }
  async function removeSelectedRoster() {
    const sel = (rosterGrid.current?.api?.getSelectedRows() ?? []) as Station[];
    if (sel.length === 0) return;
    if (!window.confirm(t('ncp.removeConfirm', { n: sel.length }))) return;
    try {
      for (const s of sel) await NetRosterRemove(selId, s.callsign);
      await refreshRoster(selId);
    } catch (e: any) { setError(String(e?.message ?? e)); }
  }

  const activeCols = useMemo<ColDef<QSOForm>[]>(() => [
    { headerName: t('ncp.colCallsign'), field: 'callsign', width: 110, cellClass: 'font-mono font-semibold' },
    { headerName: t('ncp.colName'), field: 'name', flex: 1 },
    { headerName: 'QTH', field: 'qth', flex: 1 },
    { headerName: t('ncp.colTimeOn'), valueGetter: (p) => fmtTimeOn((p.data as any)?.qso_date), width: 90, cellClass: 'font-mono text-[11px]' },
    { headerName: t('ncp.colBand'), field: 'band', width: 70, cellClass: 'font-mono' },
    { headerName: t('ncp.colMode'), field: 'mode', width: 70, cellClass: 'font-mono' },
    { headerName: 'RST S', field: 'rst_sent', width: 70, cellClass: 'font-mono' },
    { headerName: 'RST R', field: 'rst_rcvd', width: 70, cellClass: 'font-mono' },
    { headerName: t('ncp.colComment'), field: 'comment', flex: 1.5 },
  ], [t]);

  const rosterCols = useMemo<ColDef<Station>[]>(() => [
    { headerName: t('ncp.colCallsign'), field: 'callsign', width: 110, cellClass: 'font-mono font-semibold' },
    { headerName: t('ncp.colName'), field: 'name', flex: 1 },
    { headerName: 'QTH', field: 'qth', flex: 1 },
    { headerName: t('ncp.colCountry'), field: 'country', width: 130 },
  ], [t]);

  const defaultColDef = useMemo<ColDef>(() => ({ sortable: true, resizable: true, suppressMovable: false }), []);

  return (
    <div className="flex flex-col min-h-0 flex-1">
      {/* Toolbar */}
      <div className="flex items-center gap-2 px-3 py-2 border-b border-border/60 bg-muted/30">
        <Button variant="outline" size="sm" className="h-8" onClick={newNet}>
          <Plus className="size-3.5" /> {t('ncp.newNet')}
        </Button>
        <select
          className="h-8 rounded-md border border-input bg-background px-2 text-sm min-w-[180px]"
          value={selId}
          disabled={isOpen}
          onChange={(e) => setSelId(e.target.value)}
          title={isOpen ? t('ncp.closeToSwitch') : t('ncp.selectNetTitle')}
        >
          <option value="">{t('ncp.selectNetOption')}</option>
          {nets.map((n) => <option key={n.id} value={n.id}>{n.name}</option>)}
        </select>
        <Button variant={isOpen ? 'destructive' : 'default'} size="sm" className="h-8" disabled={!selId} onClick={toggleOpen}>
          <Radio className="size-3.5" /> {isOpen ? t('ncp.closeNet') : t('ncp.openNet')}
        </Button>
        <Button variant="ghost" size="sm" className="h-8" disabled={!selId || isOpen} onClick={renameNet}>{t('ncp.rename')}</Button>
        <Button variant="ghost" size="sm" className="h-8 text-danger" disabled={!selId || isOpen} onClick={deleteNet}>
          <Trash2 className="size-3.5" /> {t('ncp.delete')}
        </Button>

        <div className="flex-1" />
        {isOpen && <Badge className="bg-success text-success-foreground tracking-wider">{t('ncp.netOpenBadge')}</Badge>}
        <span className="text-[11px] text-muted-foreground">
          {t('ncp.onAir')} <strong className="text-foreground">{active.length}</strong> ·
          {t('ncp.roster')} <strong className="text-foreground">{roster.length}</strong>
        </span>
        {error && <Badge variant="destructive" className="max-w-[280px] truncate" title={error}>{error}</Badge>}
      </div>

      <div className="flex min-h-0 flex-1">
        {/* ACTIVE USERS (left) */}
        <div className="flex flex-col min-h-0 flex-1 border-r border-border/60">
          <div className="flex items-center gap-2 px-3 py-1.5 bg-destructive text-destructive-foreground text-[11px] font-semibold uppercase tracking-wider">
            {t('ncp.onAirActive')}
            <span className="ml-auto font-normal normal-case opacity-90">{t('ncp.activeHint')}</span>
          </div>
          <div style={{ flex: 1, minHeight: 0, position: 'relative' }}>
            <div style={{ position: 'absolute', inset: 0 }}>
              <AgGridReact<QSOForm>
                ref={activeGrid}
                theme={hamlogTheme}
                rowData={active}
                columnDefs={activeCols}
                defaultColDef={defaultColDef}
                onRowClicked={(e) => e.data && showWorkedBefore(e.data.callsign, (e.data as any).dxcc ?? 0)}
                onRowDoubleClicked={(e) => e.data && setEditingDraft(e.data)}
                rowSelection={{ mode: 'singleRow', checkboxes: false, enableClickSelection: true }}
                animateRows={false}
                getRowId={(p) => String((p.data as any).id)}
              />
            </div>
          </div>
          {isOpen && active.length > 0 && (
            <div className="px-3 py-1.5 border-t border-border/60 bg-muted/30 flex items-center gap-2">
              <Button variant="ghost" size="sm" className="h-7 text-[11px]"
                onClick={() => { const r = activeGrid.current?.api?.getSelectedRows?.()?.[0] as QSOForm; if (r) deactivate(r.id as number); }}>
                <MinusCircle className="size-3.5" /> {t('ncp.logEndSelected')}
              </Button>
            </div>
          )}

          {/* Worked-before for the clicked station — click a row (on air or
              roster) to see if/when we contacted it before. Lives INSIDE the
              on-air column so resizing only splits this column; the roster on
              the right keeps its full height. */}
          <div className="shrink-0 flex flex-col" style={{ height: wbHeight }}>
            <div className="h-1.5 shrink-0 cursor-ns-resize border-t border-border/60 hover:bg-primary/40 transition-colors" onMouseDown={startWbResize} title={t('ncp.wbResize')} />
            <div className="flex-1 min-h-0 bg-card flex flex-col">
              <div className="flex items-center gap-2 px-3 py-1.5 bg-muted/40 text-[11px] font-semibold uppercase tracking-wider text-muted-foreground shrink-0">
                <History className="size-3.5" />
                {t('ncp.workedBefore')}
                {wbCall && <span className="font-mono text-foreground normal-case">{wbCall}</span>}
                {wb && wb.count > 0 && (
                  <span className="ml-auto font-normal normal-case text-foreground">
                    {wb.count} QSO · {t('ncp.wbFirst')} {String(wb.first ?? '').slice(0, 10)} · {t('ncp.wbLast')} {String(wb.last ?? '').slice(0, 10)}
                    {wb.dxcc_name ? ` · ${wb.dxcc_name} (${wb.dxcc_count})` : ''}
                  </span>
                )}
              </div>
              <div className="flex-1 min-h-0 overflow-auto">
                {!wbCall ? (
                  <div className="flex h-full items-center justify-center text-xs text-muted-foreground">{t('ncp.wbHint')}</div>
                ) : wbBusy ? (
                  <div className="flex h-full items-center justify-center text-xs text-muted-foreground">…</div>
                ) : !wb || wb.count === 0 ? (
                  <div className="flex h-full items-center justify-center text-xs text-muted-foreground">{t('ncp.wbNone')} {wbCall}</div>
                ) : (
                  <table className="w-full text-xs">
                    <thead className="sticky top-0 bg-muted/30 text-muted-foreground">
                      <tr className="text-left">
                        <th className="px-3 py-1 font-semibold">{t('ncp.colDate')}</th>
                        <th className="px-2 py-1 font-semibold">{t('ncp.colTimeOn')}</th>
                        <th className="px-2 py-1 font-semibold">{t('ncp.colBand')}</th>
                        <th className="px-2 py-1 font-semibold">{t('ncp.colMode')}</th>
                        <th className="px-2 py-1 font-semibold">RST</th>
                        <th className="px-2 py-1 font-semibold">{t('ncp.colComment')}</th>
                      </tr>
                    </thead>
                    <tbody>
                      {((wb.entries ?? []) as any[]).map((q, i) => (
                        <tr key={q.id ?? i} className="border-t border-border/20 hover:bg-muted/30">
                          <td className="px-3 py-1 font-mono">{String(q.qso_date ?? '').slice(0, 10)}</td>
                          <td className="px-2 py-1 font-mono">{String(q.time_on ?? '').slice(0, 5)}</td>
                          <td className="px-2 py-1">{q.band}</td>
                          <td className="px-2 py-1">{q.mode}</td>
                          <td className="px-2 py-1 font-mono">{(q.rst_sent ?? '')}/{(q.rst_rcvd ?? '')}</td>
                          <td className="px-2 py-1 truncate max-w-[360px]">{q.comment}</td>
                        </tr>
                      ))}
                    </tbody>
                  </table>
                )}
              </div>
            </div>
          </div>
        </div>

        {/* NET USERS / roster (right) */}
        <div className="flex flex-col min-h-0 w-[40%] max-w-[560px]">
          <div className="flex items-center gap-2 px-3 py-1.5 bg-success text-success-foreground text-[11px] font-semibold uppercase tracking-wider">
            {t('ncp.netUsersRoster')}
            <span className="ml-auto font-normal normal-case opacity-90">{t('ncp.rosterHint')}</span>
          </div>
          <div style={{ flex: 1, minHeight: 0, position: 'relative' }}>
            <div style={{ position: 'absolute', inset: 0 }}>
              <AgGridReact<Station>
                ref={rosterGrid}
                theme={hamlogTheme}
                rowData={rosterShown}
                columnDefs={rosterCols}
                defaultColDef={defaultColDef}
                rowSelection={{ mode: 'multiRow', checkboxes: false, headerCheckbox: false, enableClickSelection: true }}
                onRowClicked={(e) => e.data && showWorkedBefore(e.data.callsign, (e.data as any).dxcc ?? 0)}
                onRowDoubleClicked={(e) => e.data && activate(e.data.callsign)}
                animateRows={false}
                getRowId={(p) => String((p.data as any).callsign)}
              />
            </div>
          </div>
          <div className="px-3 py-1.5 border-t border-border/60 bg-muted/30 flex items-center gap-2">
            <Button variant="ghost" size="sm" className="h-7 text-[11px]" disabled={!selId} onClick={openAddContact}>
              <UserPlus className="size-3.5" /> {t('ncp.addContact')}
            </Button>
            <Button variant="ghost" size="sm" className="h-7 text-[11px] text-danger" disabled={!selId} onClick={removeSelectedRoster}>
              <MinusCircle className="size-3.5" /> {t('ncp.remove')}
            </Button>
            {isOpen && (
              <Button variant="ghost" size="sm" className="h-7 text-[11px] ml-auto"
                onClick={() => { const r = rosterGrid.current?.api?.getSelectedRows?.()?.[0] as Station; if (r) activate(r.callsign); }}>
                <PlusCircle className="size-3.5" /> {t('ncp.putOnAir')}
              </Button>
            )}
          </div>
        </div>
      </div>

      {/* Full-QSO edit modal for the selected on-air draft. Save writes back to
          the in-memory draft (NetUpdateActive); Delete cancels it (no log). */}
      {editingDraft && (
        <QSOEditModal
          qso={editingDraft}
          onSave={saveDraft}
          onDelete={discardDraft}
          onClose={() => setEditingDraft(null)}
          countries={countries}
          bands={bands}
          modes={modes}
        />
      )}

      {/* Add / edit contact dialog */}
      <Dialog open={contactOpen} onOpenChange={setContactOpen}>
        <DialogContent className="max-w-md">
          <DialogHeader>
            <DialogTitle>{t('ncp.addContactTitle')}</DialogTitle>
            <DialogDescription>{t('ncp.addContactDesc')}</DialogDescription>
          </DialogHeader>
          <div className="grid gap-3 px-5 py-2">
            <div className="flex items-end gap-2">
              <div className="flex-1">
                <Label className="text-[11px]">{t('ncp.callsign')}</Label>
                <Input className="font-mono uppercase" value={contact.callsign ?? ''}
                  onChange={(e) => setContact((c) => netctl.Station.createFrom({ ...c, callsign: e.target.value.toUpperCase() }))}
                  onKeyDown={(e) => { if (e.key === 'Enter') lookupContact(); }} />
              </div>
              <Button variant="outline" size="sm" className="h-9" onClick={lookupContact} disabled={looking || !(contact.callsign ?? '').trim()}>
                <Search className="size-3.5" /> {looking ? '…' : t('ncp.search')}
              </Button>
            </div>
            <div>
              <Label className="text-[11px]">{t('ncp.name')}</Label>
              <Input value={contact.name ?? ''} onChange={(e) => setContact((c) => netctl.Station.createFrom({ ...c, name: e.target.value }))} />
            </div>
            <div>
              <Label className="text-[11px]">QTH</Label>
              <Input value={contact.qth ?? ''} onChange={(e) => setContact((c) => netctl.Station.createFrom({ ...c, qth: e.target.value }))} />
            </div>
            <div>
              <Label className="text-[11px]">{t('ncp.country')}</Label>
              <Input value={contact.country ?? ''} onChange={(e) => setContact((c) => netctl.Station.createFrom({ ...c, country: e.target.value }))} />
            </div>
          </div>
          <DialogFooter>
            <Button variant="ghost" size="sm" onClick={() => setContactOpen(false)}>{t('ncp.cancel')}</Button>
            <Button size="sm" onClick={saveContact} disabled={!(contact.callsign ?? '').trim()}>
              <PlusCircle className="size-3.5" /> {t('ncp.saveInNet')}
            </Button>
          </DialogFooter>
        </DialogContent>
      </Dialog>
    </div>
  );
}
