import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import {
  AllCommunityModule, ModuleRegistry,
  type ColDef, type ColumnState, type GridReadyEvent, type RowDoubleClickedEvent,
} from 'ag-grid-community';
import { hamlogGridTheme } from '@/lib/gridTheme';
import { AgGridReact } from 'ag-grid-react';
import { Columns3, FilterX, Star } from 'lucide-react';
import {
  Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter, DialogDescription,
} from '@/components/ui/dialog';
import { Button } from '@/components/ui/button';
import { Checkbox } from '@/components/ui/checkbox';
import { Badge } from '@/components/ui/badge';
import type { WorkedBeforeView, QSOForm } from '@/types';
import { makeColCatalog, GROUP_ORDER, groupLabel } from './RecentQSOsGrid';
import { QSOContextMenu, type QSOMenuState } from './QSOContextMenu';
import { loadLocal, loadRemote, saveState, seedLocal, whenGridPrefsReady } from '@/lib/gridPrefs';
import { useI18n } from '@/lib/i18n';

ModuleRegistry.registerModules([AllCommunityModule]);

const hamlogTheme = hamlogGridTheme;

type WorkedEntry = QSOForm; // entries are now full QSO records

type Props = {
  // Operator's CURRENT locator — fallback for the distance column (see catalog).
  myGrid?: string;
  wb: WorkedBeforeView | null;
  busy: boolean;
  currentCall: string;
  onRowDoubleClicked?: (q: QSOForm) => void;
  onUpdateFromCty?: (ids: number[]) => void;
  onUpdateFromQRZ?: (ids: number[]) => void;
  onUpdateFromClublog?: (ids: number[]) => void;
  onSendTo?: (service: string, ids: number[]) => void;
  onSendRecording?: (ids: number[]) => void;
  onSendEQSL?: (ids: number[]) => void;
  onBulkEdit?: (ids: number[]) => void;
  onExportSelected?: (ids: number[]) => void;
  onExportSelectedFields?: (ids: number[]) => void;
  onExportCabrilloSelected?: (ids: number[]) => void;
  onDelete?: (ids: number[]) => void;
  // One column per defined award (cell = the reference this QSO counts for).
  awardCols?: { code: string; name: string }[];
};

const COL_STATE_KEY = 'hamlog.workedBeforeColState.v2';

function fmtDate(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 `${d.getUTCFullYear()}-${p(d.getUTCMonth() + 1)}-${p(d.getUTCDate())}`;
}

export function WorkedBeforeGrid({ wb, myGrid, busy, currentCall, onRowDoubleClicked, onUpdateFromCty, onUpdateFromQRZ, onUpdateFromClublog, onSendTo, onSendRecording, onSendEQSL, onBulkEdit, onExportSelected, onExportSelectedFields, onExportCabrilloSelected, onDelete, awardCols }: Props) {
  const { t } = useI18n();
  const gridRef = useRef<any>(null);
  const [pickerOpen, setPickerOpen] = useState(false);
  const [menu, setMenu] = useState<QSOMenuState>(null);

  // Localized column catalog (shared with the Recent QSOs grid).
  const COL_CATALOG = useMemo(() => makeColCatalog(t, myGrid), [t, myGrid]);

  function handleRowDoubleClicked(e: RowDoubleClickedEvent<WorkedEntry>) {
    if (e.data && onRowDoubleClicked) onRowDoubleClicked(e.data);
  }
  function onCellContextMenu(e: any) {
    const ev = e.event as MouseEvent | undefined;
    ev?.preventDefault();
    const api = gridRef.current?.api;
    if (!api) return;
    if (e.node && !e.node.isSelected()) {
      api.deselectAll();
      e.node.setSelected(true);
    }
    const ids = (api.getSelectedRows() as WorkedEntry[])
      .map((r) => r.id as number)
      .filter((n) => !!n);
    if (ids.length === 0) return;
    setMenu({ x: ev?.clientX ?? 0, y: ev?.clientY ?? 0, ids });
  }

  const hasCall = currentCall.trim() !== '';
  const count = wb?.count ?? 0;
  const entries = wb?.entries ?? [];

  // Suppress auto-save while AG Grid rebuilds columns (award columns load async),
  // so the default visibility doesn't clobber the user's saved state. See the
  // matching note in RecentQSOsGrid.
  const restoringRef = useRef(true);
  const columnDefs = useMemo<ColDef<WorkedEntry>[]>(() => {
    restoringRef.current = true;
    const base = COL_CATALOG.map((c) => {
      const { group: _g, label: _l, defaultVisible, ...rest } = c;
      return { ...rest, hide: !defaultVisible };
    });
    const awards: ColDef<WorkedEntry>[] = (awardCols ?? []).map((a) => ({
      colId: `award_${a.code}`,
      headerName: a.code,
      headerTooltip: t('wbg.awardTip', { name: a.name }),
      width: 110,
      cellClass: 'text-[11px]',
      valueGetter: (p) => (p.data as any)?.award_refs?.[a.code.toUpperCase()] ?? '',
    }));
    return [...base, ...awards];
  }, [awardCols, COL_CATALOG, t]);

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

  // Restore AFTER the profile scope is known: this grid has no key= remount to
  // save it from reading the wrong (unscoped) cache key at first paint, so it
  // used to miss the cache every time and then save under the scoped key.
  async function onGridReady(e: GridReadyEvent) {
    await whenGridPrefsReady();
    const local = loadLocal(COL_STATE_KEY);
    if (local) e.api.applyColumnState({ state: local as ColumnState[], applyOrder: true });
    const remote = await loadRemote(COL_STATE_KEY);
    if (remote && !local) {
      e.api.applyColumnState({ state: remote as ColumnState[], applyOrder: true });
      seedLocal(COL_STATE_KEY, remote);
    }
  }
  const saveColumnState = useCallback(() => {
    if (restoringRef.current) return; // ignore events fired by a column rebuild
    const state = gridRef.current?.api?.getColumnState();
    if (state) saveState(COL_STATE_KEY, state);
  }, []);

  // Re-apply the saved column state after the award columns load (they rebuild
  // the column set), so the user's visibility choices win over the defaults.
  useEffect(() => {
    const api = gridRef.current?.api;
    const local = loadLocal(COL_STATE_KEY);
    if (api && local) api.applyColumnState({ state: local as ColumnState[], applyOrder: true });
    const t = window.setTimeout(() => { restoringRef.current = false; }, 0);
    return () => window.clearTimeout(t);
    // columnDefs itself, not the reasons it was rebuilt — see the same note in
    // RecentQSOsGrid: a language change rebuilt the memo and left saving off.
  }, [columnDefs]);

  function isColVisible(colId: string): boolean {
    const col = gridRef.current?.api?.getColumn(colId);
    return col ? col.isVisible() : !!COL_CATALOG.find((c) => c.colId === colId)?.defaultVisible;
  }
  function setColVisible(colId: string, visible: boolean) {
    const api = gridRef.current?.api;
    if (!api) return;
    api.setColumnsVisible([colId], visible);
    saveColumnState();
  }
  function showAll(group?: string) {
    const api = gridRef.current?.api;
    if (!api) return;
    const ids = COL_CATALOG.filter((c) => !group || c.group === group).map((c) => c.colId!);
    api.setColumnsVisible(ids, true);
    saveColumnState();
  }
  function hideAll(group?: string) {
    const api = gridRef.current?.api;
    if (!api) return;
    const ids = COL_CATALOG.filter((c) => !group || c.group === group).map((c) => c.colId!);
    api.setColumnsVisible(ids, false);
    saveColumnState();
  }
  function resetDefaults() {
    const api = gridRef.current?.api;
    if (!api) return;
    const visible = COL_CATALOG.filter((c) => c.defaultVisible).map((c) => c.colId!);
    const hidden = COL_CATALOG.filter((c) => !c.defaultVisible).map((c) => c.colId!);
    api.setColumnsVisible(visible, true);
    api.setColumnsVisible(hidden, false);
    saveColumnState();
  }

  // Empty / loading / no-call states.
  if (!hasCall) {
    return (
      <div className="flex-1 flex flex-col items-center justify-center gap-2 p-6 text-center text-xs text-muted-foreground">
        {t('wbg.typeCall')}
      </div>
    );
  }
  if (busy && count === 0) {
    return (
      <div className="flex-1 flex items-center justify-center text-xs text-muted-foreground italic">
        {t('wbg.checking')}
      </div>
    );
  }
  if (count === 0) {
    return (
      <div className="flex-1 flex flex-col items-center justify-center gap-2 p-6 text-center text-xs text-muted-foreground">
        <Star className="size-8 text-primary fill-current" />
        <div className="text-2xl font-bold text-primary tracking-wider">{t('wbg.new')}</div>
        <div>{t('wbg.noPriorPre')}<span className="font-mono font-semibold text-foreground">{currentCall.toUpperCase()}</span>{t('wbg.noPriorPost')}</div>
      </div>
    );
  }

  return (
    <>
      <div className="flex items-center gap-3 px-3 py-1.5 border-b border-border/60 bg-muted/30">
        <div className="flex items-baseline gap-2">
          <span className="text-[11px] font-semibold uppercase tracking-wider text-muted-foreground">{t('wbg.workedBefore')}</span>
          <span className="font-mono text-sm font-bold text-primary tracking-wider">{currentCall.toUpperCase()}</span>
          <Badge variant="accent" className="font-mono text-[11px] tracking-wider">{count}×</Badge>
        </div>
        <div className="text-[11px] text-muted-foreground">
          {t('wbg.first')} <strong className="text-foreground font-semibold">{fmtDate(wb?.first)}</strong> ·{' '}
          {t('wbg.last')} <strong className="text-foreground font-semibold">{fmtDate(wb?.last)}</strong>
        </div>
        {wb?.dxcc_name && (
          <div className="text-[11px] text-muted-foreground">
            {t('wbg.dxcc')} <strong className="text-foreground font-semibold">{wb.dxcc_name}</strong>
            {typeof wb.dxcc_count === 'number' && wb.dxcc_count > 0 && (
              <span> · {t('wbg.entityQsos', { n: wb.dxcc_count })}</span>
            )}
          </div>
        )}
        <div className="flex-1" />
        <Button variant="ghost" size="sm" className="h-7 text-[11px]" onClick={() => gridRef.current?.api?.setFilterModel(null)}
          title={t('wbg.clearFiltersTitle')}>
          <FilterX className="size-3.5" /> {t('wbg.clearFilters')}
        </Button>
        <Button variant="ghost" size="sm" className="h-7 text-[11px]" onClick={() => setPickerOpen(true)}>
          <Columns3 className="size-3.5" /> {t('wbg.columns')}
        </Button>
      </div>

      <div style={{ flex: 1, minHeight: 0, position: 'relative' }}>
        <div style={{ position: 'absolute', inset: 0 }}>
          <AgGridReact<WorkedEntry>
            ref={gridRef}
            theme={hamlogTheme}
            rowData={entries}
            columnDefs={columnDefs}
            defaultColDef={defaultColDef}
            onGridReady={onGridReady}
            onColumnResized={saveColumnState}
            onColumnMoved={saveColumnState}
            onColumnPinned={saveColumnState}
            onColumnVisible={saveColumnState}
            onSortChanged={saveColumnState}
            onRowDoubleClicked={handleRowDoubleClicked}
            onCellContextMenu={onCellContextMenu}
            preventDefaultOnContextMenu
            rowSelection={{ mode: 'multiRow', checkboxes: false, headerCheckbox: false, enableClickSelection: true }}
            animateRows={false}
            suppressCellFocus
            getRowId={(p) => String((p.data as any).id)}
          />
        </div>
      </div>

      {/* Same menu as Recent QSOs, minus the two "filtered" exports: there they
          mean "the whole logbook under the active column filters", but this grid
          is a per-callsign view rather than a filter over the log, so the entry
          would quietly export everything — not what it would appear to do here.
          The selection-based exports and bulk edit apply unchanged. */}
      <QSOContextMenu
        menu={menu}
        onClose={() => setMenu(null)}
        onUpdateFromCty={(ids) => onUpdateFromCty?.(ids)}
        onUpdateFromQRZ={(ids) => onUpdateFromQRZ?.(ids)}
        onUpdateFromClublog={onUpdateFromClublog}
        onSendTo={onSendTo}
        onSendRecording={onSendRecording}
        onSendEQSL={onSendEQSL}
        onBulkEdit={onBulkEdit}
        onExportSelected={onExportSelected}
        onExportSelectedFields={onExportSelectedFields}
        onExportCabrilloSelected={onExportCabrilloSelected}
        onDelete={onDelete}
      />

      {count > entries.length && (
        <div className="text-center py-1 text-[11px] italic text-muted-foreground border-t border-border/60 bg-muted/30">
          {t('wbg.olderQsos', { n: count - entries.length })}
        </div>
      )}

      <Dialog open={pickerOpen} onOpenChange={setPickerOpen}>
        <DialogContent className="max-w-2xl">
          <DialogHeader>
            <DialogTitle>{t('wbg.pickerTitle')}</DialogTitle>
            <DialogDescription>
              {t('wbg.pickerDesc')}
            </DialogDescription>
          </DialogHeader>
          <div className="grid grid-cols-2 gap-4 max-h-[60vh] overflow-y-auto px-5 py-3">
            {GROUP_ORDER.map((group) => {
              const cols = COL_CATALOG.filter((c) => c.group === group);
              if (cols.length === 0) return null;
              return (
                <div key={group} className="rounded-md border border-border p-2.5">
                  <div className="flex items-center justify-between mb-2 pb-1.5 border-b border-border/60">
                    <span className="text-[11px] font-semibold uppercase tracking-wider text-muted-foreground">{groupLabel(t, group)}</span>
                    <div className="flex gap-0.5">
                      <button className="text-[10px] text-primary hover:underline px-1" onClick={() => showAll(group)}>{t('wbg.all')}</button>
                      <button className="text-[10px] text-muted-foreground hover:underline px-1" onClick={() => hideAll(group)}>{t('wbg.none')}</button>
                    </div>
                  </div>
                  <div className="flex flex-col gap-1">
                    {cols.map((c) => (
                      <label key={c.colId} className="flex items-center gap-2 text-xs cursor-pointer hover:bg-accent/30 rounded px-1 py-0.5">
                        <Checkbox
                          checked={isColVisible(c.colId!)}
                          onCheckedChange={(v) => setColVisible(c.colId!, !!v)}
                        />
                        {c.label}
                      </label>
                    ))}
                  </div>
                </div>
              );
            })}
            {awardCols && awardCols.length > 0 && (
              <div className="rounded-md border border-border p-2.5">
                <div className="flex items-center justify-between mb-2 pb-1.5 border-b border-border/60">
                  <span className="text-[11px] font-semibold uppercase tracking-wider text-muted-foreground">{t('wbg.grpAwards')}</span>
                  <div className="flex gap-0.5">
                    <button className="text-[10px] text-primary hover:underline px-1" onClick={() => { awardCols.forEach((a) => setColVisible(`award_${a.code}`, true)); }}>{t('wbg.all')}</button>
                    <button className="text-[10px] text-muted-foreground hover:underline px-1" onClick={() => { awardCols.forEach((a) => setColVisible(`award_${a.code}`, false)); }}>{t('wbg.none')}</button>
                  </div>
                </div>
                <div className="flex flex-col gap-1">
                  {awardCols.map((a) => (
                    <label key={a.code} className="flex items-center gap-2 text-xs cursor-pointer hover:bg-accent/30 rounded px-1 py-0.5">
                      <Checkbox checked={isColVisible(`award_${a.code}`)} onCheckedChange={(v) => setColVisible(`award_${a.code}`, !!v)} />
                      <span className="font-mono font-semibold">{a.code}</span>
                      <span className="text-muted-foreground truncate">{a.name}</span>
                    </label>
                  ))}
                </div>
              </div>
            )}
          </div>
          <DialogFooter>
            <Button variant="ghost" size="sm" onClick={resetDefaults}>{t('wbg.resetDefaults')}</Button>
            <Button size="sm" onClick={() => setPickerOpen(false)}>{t('wbg.done')}</Button>
          </DialogFooter>
        </DialogContent>
      </Dialog>
    </>
  );
}
