import { useEffect, useMemo, useRef, useState, type ComponentType } from 'react';
import { useNavigate } from 'react-router-dom';
import {
  BarChart2,
  Calendar,
  CreditCard,
  Loader2,
  Navigation,
  Plus,
  Receipt,
  Search,
  Settings,
  Snowflake,
  Tag,
  Upload,
  User,
  X,
  Landmark,
  ArrowRightLeft,
  Download,
} from 'lucide-react';
import { toast } from 'sonner';
import { api } from '@/api';
import { cn, fmt, errMessage } from '@/lib/utils';
import { scheduleLabel, scheduleValue } from '@/lib/billingSchedule';
import { Button } from '@/components/ui/button';
import { Dialog, DialogContent, DialogHeader, DialogTitle } from '@/components/ui/dialog';
import { Input } from '@/components/ui/input';
import { BrandGlyph } from '@/components/brand/Brand';
import type { Bill } from '@/types';

// ─── Navigation commands ──────────────────────────────────────────────────────

interface Command {
  id: string;
  label: string;
  icon: ComponentType<{ className?: string }>;
  path: string;
  group: string;
}

const MONTHS = [
  'January',
  'February',
  'March',
  'April',
  'May',
  'June',
  'July',
  'August',
  'September',
  'October',
  'November',
  'December',
];

const NAV_COMMANDS: Command[] = [
  { id: 'nav-tracker', label: 'Go to Tracker', icon: Receipt, path: '/', group: 'Navigate' },
  { id: 'nav-bills', label: 'Go to Bills', icon: CreditCard, path: '/bills', group: 'Navigate' },
  {
    id: 'nav-calendar',
    label: 'Go to Calendar',
    icon: Calendar,
    path: '/calendar',
    group: 'Navigate',
  },
  {
    id: 'nav-summary',
    label: 'Go to Summary',
    icon: BarChart2,
    path: '/summary',
    group: 'Navigate',
  },
  {
    id: 'nav-analytics',
    label: 'Go to Analytics',
    icon: BarChart2,
    path: '/analytics',
    group: 'Navigate',
  },
  {
    id: 'nav-snowball',
    label: 'Go to Snowball',
    icon: Snowflake,
    path: '/snowball',
    group: 'Navigate',
  },
  {
    id: 'nav-categories',
    label: 'Go to Categories',
    icon: Tag,
    path: '/categories',
    group: 'Navigate',
  },
  { id: 'nav-data', label: 'Go to Data', icon: Upload, path: '/data', group: 'Navigate' },
  {
    id: 'nav-data-bank',
    label: 'Data: Bank sync',
    icon: Landmark,
    path: '/data?section=bank-sync',
    group: 'Navigate',
  },
  {
    id: 'nav-data-tx',
    label: 'Data: Transactions',
    icon: ArrowRightLeft,
    path: '/data?section=transactions',
    group: 'Navigate',
  },
  {
    id: 'nav-data-import',
    label: 'Data: Import',
    icon: Upload,
    path: '/data?section=import',
    group: 'Navigate',
  },
  {
    id: 'nav-data-export',
    label: 'Data: Export & backups',
    icon: Download,
    path: '/data?section=export',
    group: 'Navigate',
  },
  {
    id: 'nav-settings',
    label: 'Go to Settings',
    icon: Settings,
    path: '/settings',
    group: 'Navigate',
  },
  { id: 'nav-profile', label: 'Go to Profile', icon: User, path: '/profile', group: 'Navigate' },
  {
    id: 'action-new-bill',
    label: 'Add a new bill',
    icon: Plus,
    path: '/bills?new=1',
    group: 'Actions',
  },
];

// Generate jump-to-month commands for the current year ± 1
function buildMonthCommands(): Command[] {
  const now = new Date();
  const year = now.getFullYear();
  const commands: Command[] = [];
  for (let y = year - 1; y <= year + 1; y++) {
    for (let m = 1; m <= 12; m++) {
      commands.push({
        id: `jump-${y}-${m}`,
        label: `Jump to ${MONTHS[m - 1]} ${y}`,
        icon: Calendar,
        path: `/?year=${y}&month=${m}`,
        group: 'Jump to Month',
      });
    }
  }
  return commands;
}

// ─── Helpers ──────────────────────────────────────────────────────────────────

function amountSearchText(...values: unknown[]): string {
  return values
    .filter((value) => value !== null && value !== undefined && Number.isFinite(Number(value)))
    .flatMap((value) => {
      const num = Number(value);
      return [String(num), num.toFixed(2), `$${num.toFixed(2)}`];
    })
    .join(' ');
}

function billSearchText(bill: Bill): string {
  return [
    bill.name,
    bill.category_name,
    bill.notes,
    scheduleValue(bill),
    scheduleLabel(bill),
    bill.bucket,
    bill.website,
    amountSearchText(
      bill.expected_amount,
      bill.current_balance,
      bill.minimum_payment,
      bill.interest_rate,
    ),
  ]
    .filter(Boolean)
    .join(' ')
    .toLowerCase();
}

function shortcutLabel(): string {
  if (typeof navigator !== 'undefined' && /Mac|iPhone|iPad|iPod/i.test(navigator.platform)) {
    return 'Cmd K';
  }
  return 'Ctrl K';
}

// ─── Result item components ───────────────────────────────────────────────────

function BillResult({
  bill,
  onOpenBills,
  onOpenTracker,
}: {
  bill: Bill;
  onOpenBills: (bill: Bill) => void;
  onOpenTracker: (bill: Bill) => void;
}) {
  return (
    <div
      className={cn(
        'group grid gap-2 rounded-lg border border-transparent p-2 transition-colors',
        'hover:border-border/70 hover:bg-accent/65 sm:grid-cols-[1fr_auto]',
      )}
    >
      <button
        type="button"
        onClick={() => onOpenBills(bill)}
        className="flex min-w-0 items-center gap-3 rounded-md text-left focus-visible:outline-none focus-visible:ring-[3px] focus-visible:ring-ring/50"
      >
        <span className="flex h-9 w-9 shrink-0 items-center justify-center rounded-md bg-primary/10 text-primary">
          <Receipt className="h-4 w-4" />
        </span>
        <span className="min-w-0">
          <span className="flex min-w-0 items-center gap-2">
            <span className="truncate text-sm font-semibold text-foreground">{bill.name}</span>
            {!bill.active && (
              <span className="shrink-0 rounded-full border border-border px-1.5 py-0.5 text-[10px] font-medium text-muted-foreground">
                Inactive
              </span>
            )}
          </span>
          <span className="mt-0.5 flex flex-wrap items-center gap-x-2 gap-y-0.5 text-xs text-muted-foreground">
            <span>{bill.category_name || 'Uncategorized'}</span>
            <span>Due {bill.due_day}</span>
            <span>{fmt(bill.expected_amount)}</span>
          </span>
        </span>
      </button>
      <div className="flex items-center justify-end gap-1 pl-12 sm:pl-0">
        <Button
          type="button"
          size="sm"
          variant="ghost"
          className="h-8 px-2.5 text-xs"
          onClick={() => onOpenTracker(bill)}
        >
          Tracker
        </Button>
        <Button
          type="button"
          size="sm"
          variant="default"
          className="h-8 px-2.5 text-xs"
          onClick={() => onOpenBills(bill)}
        >
          Bills
        </Button>
      </div>
    </div>
  );
}

function CommandResult({ cmd, onRun }: { cmd: Command; onRun: (cmd: Command) => void }) {
  const Icon = cmd.icon || Navigation;
  return (
    <button
      type="button"
      onClick={() => onRun(cmd)}
      className={cn(
        'flex w-full items-center gap-3 rounded-lg border border-transparent p-2 text-left transition-colors',
        'hover:border-border/70 hover:bg-accent/65 focus-visible:outline-none focus-visible:ring-[3px] focus-visible:ring-ring/50',
      )}
    >
      <span className="flex h-9 w-9 shrink-0 items-center justify-center rounded-md bg-muted/60 text-muted-foreground">
        <Icon className="h-4 w-4" />
      </span>
      <span className="min-w-0">
        <span className="block truncate text-sm font-medium text-foreground">{cmd.label}</span>
        <span className="mt-0.5 block text-xs text-muted-foreground">{cmd.group}</span>
      </span>
    </button>
  );
}

// ─── Main component ───────────────────────────────────────────────────────────

export default function CommandPalette() {
  const navigate = useNavigate();
  const inputRef = useRef<HTMLInputElement>(null);
  const [open, setOpen] = useState(false);
  const [query, setQuery] = useState('');
  const [bills, setBills] = useState<Bill[]>([]);
  const [loading, setLoading] = useState(false);
  const [loaded, setLoaded] = useState(false);
  const monthCommands = useMemo(() => buildMonthCommands(), []);

  useEffect(() => {
    const openPalette = () => setOpen(true);
    const onKeyDown = (event: KeyboardEvent) => {
      if ((event.ctrlKey || event.metaKey) && event.key.toLowerCase() === 'k') {
        event.preventDefault();
        setOpen((value) => !value);
      }
    };

    window.addEventListener('command-palette:open', openPalette);
    window.addEventListener('keydown', onKeyDown);
    return () => {
      window.removeEventListener('command-palette:open', openPalette);
      window.removeEventListener('keydown', onKeyDown);
    };
  }, []);

  useEffect(() => {
    if (!open) return;
    window.setTimeout(() => inputRef.current?.focus(), 0);
    if (loaded || loading) return;

    setLoading(true);
    api
      .allBills({ inactive: true })
      .then((rows) => {
        setBills(Array.isArray(rows) ? rows : []);
        setLoaded(true);
      })
      .catch((err) => {
        toast.error(errMessage(err, 'Failed to load bills'));
      })
      .finally(() => setLoading(false));
  }, [loaded, loading, open]);

  const close = () => {
    setOpen(false);
    setQuery('');
  };

  const openBills = (bill: Bill) => {
    const params = new URLSearchParams({ search: bill.name });
    if (!bill.active) params.set('inactive', '1');
    navigate(`/bills?${params.toString()}`);
    close();
  };

  const openTracker = (bill: Bill) => {
    const params = new URLSearchParams({ search: bill.name });
    navigate(`/?${params.toString()}`);
    close();
  };

  const runCommand = (cmd: Command) => {
    navigate(cmd.path);
    close();
  };

  const allCommands = useMemo(() => [...NAV_COMMANDS, ...monthCommands], [monthCommands]);

  const { billResults, commandResults } = useMemo(() => {
    const q = query.trim().toLowerCase();

    if (!q) {
      const sortedBills = [...bills]
        .sort((a, b) => {
          if (!!a.active !== !!b.active) return a.active ? -1 : 1;
          return String(a.name || '').localeCompare(String(b.name || ''));
        })
        .slice(0, 6);
      return {
        billResults: sortedBills,
        commandResults: NAV_COMMANDS.slice(0, 4),
      };
    }

    const matchedBills = [...bills]
      .filter((bill) => billSearchText(bill).includes(q))
      .sort((a, b) => {
        if (!!a.active !== !!b.active) return a.active ? -1 : 1;
        return String(a.name || '').localeCompare(String(b.name || ''));
      })
      .slice(0, 8);

    const matchedCommands = allCommands
      .filter((cmd) => cmd.label.toLowerCase().includes(q) || cmd.group.toLowerCase().includes(q))
      .slice(0, 5);

    return { billResults: matchedBills, commandResults: matchedCommands };
  }, [bills, query, allCommands]);

  const hasResults = billResults.length > 0 || commandResults.length > 0;

  return (
    <Dialog open={open} onOpenChange={setOpen}>
      <DialogContent className="gap-0 overflow-hidden border-border/70 bg-card/95 p-0 shadow-2xl shadow-black/20 backdrop-blur-xl sm:max-w-2xl">
        <DialogHeader className="sr-only">
          <DialogTitle>Command palette</DialogTitle>
        </DialogHeader>

        <div className="border-b border-border/70 bg-muted/25 px-4 py-3">
          <div className="mb-3 flex items-center gap-3">
            <BrandGlyph name="tracker" className="h-10 w-10 rounded-2xl" />
            <div className="min-w-0">
              <p className="text-sm font-semibold text-foreground">Find your next move</p>
              <p className="mt-0.5 text-xs text-muted-foreground">
                Search bills, jump to a month, or open a workspace.
              </p>
            </div>
          </div>
          <div className="flex items-center gap-2 rounded-xl border border-border/70 bg-card/75 px-3 py-1.5 shadow-sm shadow-black/5">
            <Search className="h-4 w-4 shrink-0 text-muted-foreground" />
            <Input
              ref={inputRef}
              value={query}
              onChange={(event) => setQuery(event.target.value)}
              onKeyDown={(event) => {
                if (event.key === 'Enter') {
                  if (billResults[0]) openBills(billResults[0]);
                  else if (commandResults[0]) runCommand(commandResults[0]);
                }
              }}
              placeholder="Search bills, months, pages..."
              className="h-9 border-0 bg-transparent px-0 text-base shadow-none focus-visible:ring-0"
            />
            {query && (
              <Button
                type="button"
                size="icon"
                variant="ghost"
                className="h-7 w-7 shrink-0 rounded-full"
                onClick={() => setQuery('')}
                aria-label="Clear search"
              >
                <X className="h-4 w-4" />
              </Button>
            )}
          </div>
        </div>

        <div className="max-h-[min(32rem,75svh)] overflow-y-auto p-2 space-y-1">
          {loading ? (
            <div className="flex items-center justify-center gap-2 px-4 py-12 text-sm text-muted-foreground">
              <Loader2 className="h-4 w-4 animate-spin" />
              Loading…
            </div>
          ) : !hasResults ? (
            <div className="px-4 py-12 text-center">
              <div className="mx-auto grid h-11 w-11 place-items-center rounded-2xl border border-border/70 bg-muted/45 text-muted-foreground">
                <Search className="h-5 w-5" aria-hidden="true" />
              </div>
              <p className="mt-3 text-sm font-medium text-foreground">
                Nothing matched that search.
              </p>
              <p className="mt-1 text-xs text-muted-foreground">
                Try a bill name, amount, category, or month.
              </p>
            </div>
          ) : (
            <>
              {commandResults.length > 0 && (
                <div>
                  <p className="mb-1 px-2 text-[10px] font-semibold uppercase tracking-widest text-muted-foreground/60">
                    Commands
                  </p>
                  <div className="space-y-0.5">
                    {commandResults.map((cmd) => (
                      <CommandResult key={cmd.id} cmd={cmd} onRun={runCommand} />
                    ))}
                  </div>
                </div>
              )}

              {billResults.length > 0 && (
                <div>
                  {commandResults.length > 0 && (
                    <p className="mt-3 mb-1 px-2 text-[10px] font-semibold uppercase tracking-widest text-muted-foreground/60">
                      Bills
                    </p>
                  )}
                  <div className="space-y-0.5">
                    {billResults.map((bill) => (
                      <BillResult
                        key={bill.id}
                        bill={bill}
                        onOpenBills={openBills}
                        onOpenTracker={openTracker}
                      />
                    ))}
                  </div>
                </div>
              )}
            </>
          )}
        </div>

        <div className="flex items-center justify-between border-t border-border/70 px-4 py-2 text-xs text-muted-foreground">
          <span>Enter to open · Tab to focus</span>
          <span>{shortcutLabel()}</span>
        </div>
      </DialogContent>
    </Dialog>
  );
}
