import { useDebouncedValue } from '@documenso/lib/client-only/hooks/use-debounced-value';
import { useSession } from '@documenso/lib/client-only/providers/session';
import { formatPath } from '@documenso/lib/constants/app';
import { SUPPORTED_LANGUAGES } from '@documenso/lib/constants/i18n';
import {
  DOCUMENTS_PAGE_SHORTCUT,
  SETTINGS_PAGE_SHORTCUT,
  TEMPLATES_PAGE_SHORTCUT,
} from '@documenso/lib/constants/keyboard-shortcuts';
import { DO_NOT_INVALIDATE_QUERY_ON_MUTATION, SKIP_QUERY_BATCH_META } from '@documenso/lib/constants/trpc';
import { dynamicActivate } from '@documenso/lib/utils/i18n';
import { trpc as trpcReact } from '@documenso/trpc/react';
import { cn } from '@documenso/ui/lib/utils';
import { Command, CommandGroup, CommandInput, CommandItem, CommandList } from '@documenso/ui/primitives/command';
import { Dialog, DialogContent } from '@documenso/ui/primitives/dialog';
import { useToast } from '@documenso/ui/primitives/use-toast';
import type { MessageDescriptor } from '@lingui/core';
import { msg } from '@lingui/core/macro';
import { useLingui } from '@lingui/react';
import { Trans } from '@lingui/react/macro';
import { keepPreviousData } from '@tanstack/react-query';
import { commandScore } from 'cmdk/dist/command-score';
import {
  ArrowLeftIcon,
  CheckIcon,
  CornerDownLeftIcon,
  FileTextIcon,
  GlobeIcon,
  KeyRoundIcon,
  LanguagesIcon,
  LayoutTemplateIcon,
  LoaderIcon,
  MonitorIcon,
  MoonIcon,
  PaletteIcon,
  SettingsIcon,
  SunIcon,
  UserIcon,
} from 'lucide-react';
import { useCallback, useEffect, useMemo, useState } from 'react';
import { useHotkeys } from 'react-hotkeys-hook';
import { Link, useNavigate } from 'react-router';
import { Theme, useTheme } from 'remix-themes';
import { match } from 'ts-pattern';

import { useOptionalCurrentTeam } from '~/providers/team';

import type { PromptCategory, PromptItem } from './app-command-menu.types';
import { useAdminSearchCategories } from './use-admin-search-categories';

/**
 * The maximum number of results the personal document/template searches return.
 */
const PERSONAL_SEARCH_RESULTS_CAP = 20;

/**
 * The minimum score for a hardcoded item to count as a fuzzy match.
 *
 * Prevents searches like "pass" showing "Templates"
 */
const MIN_FUZZY_SCORE = 0.1;

const PROMPT_GROUP_CLASSNAME =
  'border-0 p-0 pt-1 [&_[cmdk-group-heading]]:mt-0 [&_[cmdk-group-heading]]:px-2.5 [&_[cmdk-group-heading]]:pt-2 [&_[cmdk-group-heading]]:pb-1 [&_[cmdk-group-heading]]:font-semibold [&_[cmdk-group-heading]]:text-[11px] [&_[cmdk-group-heading]]:uppercase [&_[cmdk-group-heading]]:tracking-[0.07em] [&_[cmdk-group-heading]]:opacity-100';

export type AppCommandMenuProps = {
  open?: boolean;
  onOpenChange?: (_open: boolean) => void;
};

export const AppCommandMenu = ({ open, onOpenChange }: AppCommandMenuProps) => {
  const { _ } = useLingui();
  const { organisations } = useSession();

  const navigate = useNavigate();
  const currentTeam = useOptionalCurrentTeam();

  const [isOpen, setIsOpen] = useState(() => open ?? false);
  const [search, setSearch] = useState('');
  const [activePage, setActivePage] = useState<'theme' | 'language' | null>(null);
  const [activeChip, setActiveChip] = useState('all');
  const [commandValue, setCommandValue] = useState('');

  // Support both controlled and uncontrolled usage.
  const isPromptOpen = open ?? isOpen;

  const debouncedSearch = useDebouncedValue(search, 200);
  const trimmedSearch = debouncedSearch.trim();

  // cmdk keeps a stale selection value behind when the entire result list is
  // replaced, which prevents it from auto selecting the first new result.
  // Controlling the value and clearing it whenever the query changes makes
  // cmdk reliably select the first item once the new results register.
  useEffect(() => {
    setCommandValue('');
  }, [debouncedSearch, activePage]);

  const hasValidSearch = trimmedSearch.length > 0;

  const {
    data: searchDocumentsData,
    isFetching: isFetchingDocuments,
    isError: isDocumentsSearchError,
  } = trpcReact.document.search.useQuery(
    {
      query: debouncedSearch,
    },
    {
      // Sub pages filter their own local lists, so the searches pause while
      // one is open.
      enabled: isPromptOpen && activePage === null && hasValidSearch,
      placeholderData: keepPreviousData,
      // Show immediate failure instead of a long spinner.
      retry: false,

      // Do not batch this due to relatively long request time compared to
      // other queries which are generally batched with this.
      ...SKIP_QUERY_BATCH_META,
      ...DO_NOT_INVALIDATE_QUERY_ON_MUTATION,
    },
  );

  const {
    data: searchTemplatesData,
    isFetching: isFetchingTemplates,
    isError: isTemplatesSearchError,
  } = trpcReact.template.search.useQuery(
    {
      query: debouncedSearch,
    },
    {
      enabled: isPromptOpen && activePage === null && hasValidSearch,
      placeholderData: keepPreviousData,
      retry: false,
      ...SKIP_QUERY_BATCH_META,
      ...DO_NOT_INVALIDATE_QUERY_ON_MUTATION,
    },
  );

  const {
    isUserAdmin,
    categories: adminSearchCategories,
    isFetching: isFetchingAdminSearch,
    isError: isAdminSearchError,
  } = useAdminSearchCategories({
    query: trimmedSearch,
    open: isPromptOpen && activePage === null,
  });

  // Hide the page scrollbar while the prompt is open. Radix's scroll lock
  // blocks wheel and touch scrolling, but the page scrolls on the root
  // element so its scrollbar stays visible and draggable.
  useEffect(() => {
    if (!isPromptOpen) {
      return;
    }

    const scrollbarWidth = window.innerWidth - document.documentElement.clientWidth;

    const previousOverflow = document.documentElement.style.overflow;
    const previousPaddingRight = document.body.style.paddingRight;

    document.documentElement.style.overflow = 'hidden';

    // Compensate for the removed scrollbar so the page doesn't shift.
    if (scrollbarWidth > 0) {
      document.body.style.paddingRight = `${scrollbarWidth}px`;
    }

    return () => {
      document.documentElement.style.overflow = previousOverflow;
      document.body.style.paddingRight = previousPaddingRight;
    };
  }, [isPromptOpen]);

  const setOpen = useCallback(
    (nextOpen: boolean) => {
      setIsOpen(nextOpen);
      onOpenChange?.(nextOpen);

      if (!nextOpen) {
        setActivePage(null);
        setActiveChip('all');
        setSearch('');
        setCommandValue('');
      }
    },
    [onOpenChange],
  );

  const toggleOpen = () => {
    setOpen(!isPromptOpen);
  };

  const push = useCallback(
    (path: string) => {
      void navigate(path);
      setOpen(false);
    },
    [navigate, setOpen],
  );

  const goToPage = useCallback((page: 'theme' | 'language') => {
    setActivePage(page);
    setSearch('');
  }, []);

  const resolveItemLabel = useCallback(
    (label: string | MessageDescriptor) => (typeof label === 'string' ? label : _(label)),
    [_],
  );

  // Fall back to the first available team so the default view always shows
  // the document/template page links, even outside a team context such as the
  // admin pages.
  const teamUrl = useMemo(
    () => currentTeam?.url || organisations[0]?.teams[0]?.url || null,
    [currentTeam, organisations],
  );

  // Fuzzy match and rank the hardcoded items using the same scorer cmdk uses
  // internally, so abbreviations like "setg" still match "Settings".
  const filterBySearch = useCallback(
    (items: PromptItem[]) => {
      if (!hasValidSearch) {
        return items;
      }

      return items
        .map((item) => ({ item, score: commandScore(resolveItemLabel(item.label), trimmedSearch) }))
        .filter(({ score }) => score >= MIN_FUZZY_SCORE)
        .sort((a, b) => b.score - a.score)
        .map(({ item }) => item);
    },
    [hasValidSearch, trimmedSearch, resolveItemLabel],
  );

  const categories = useMemo(() => {
    const documentPageLinks: PromptItem[] = teamUrl
      ? [
          {
            id: 'documents-all',
            label: msg`All documents`,
            path: `/t/${teamUrl}/documents?status=ALL`,
            icon: FileTextIcon,
            shortcut: DOCUMENTS_PAGE_SHORTCUT.replace('+', ''),
          },
          {
            id: 'documents-draft',
            label: msg`Draft documents`,
            path: `/t/${teamUrl}/documents?status=DRAFT`,
            icon: FileTextIcon,
          },
          {
            id: 'documents-completed',
            label: msg`Completed documents`,
            path: `/t/${teamUrl}/documents?status=COMPLETED`,
            icon: FileTextIcon,
          },
          {
            id: 'documents-pending',
            label: msg`Pending documents`,
            path: `/t/${teamUrl}/documents?status=PENDING`,
            icon: FileTextIcon,
          },
          {
            id: 'documents-inbox',
            label: msg`Inbox documents`,
            path: `/t/${teamUrl}/documents?status=INBOX`,
            icon: FileTextIcon,
          },
        ]
      : [];

    const templatePageLinks: PromptItem[] = teamUrl
      ? [
          {
            id: 'templates-all',
            label: msg`All templates`,
            path: `/t/${teamUrl}/templates`,
            icon: LayoutTemplateIcon,
            shortcut: TEMPLATES_PAGE_SHORTCUT.replace('+', ''),
          },
        ]
      : [];

    const settingsLinks: PromptItem[] = [
      {
        id: 'settings-main',
        label: msg`Settings`,
        path: '/settings/profile',
        icon: SettingsIcon,
        shortcut: SETTINGS_PAGE_SHORTCUT.replace('+', ''),
      },
      { id: 'settings-profile', label: msg`Profile`, path: '/settings/profile', icon: UserIcon },
      { id: 'settings-password', label: msg`Password`, path: '/settings/security', icon: KeyRoundIcon },
      {
        id: 'settings-language',
        label: msg`Change language`,
        icon: LanguagesIcon,
        onAction: () => goToPage('language'),
      },
      { id: 'settings-theme', label: msg`Change theme`, icon: PaletteIcon, onAction: () => goToPage('theme') },
    ];

    const personalDocumentItems: PromptItem[] =
      hasValidSearch && searchDocumentsData
        ? searchDocumentsData.map((document) => ({
            id: `personal-document-${document.path}`,
            label: document.title,
            path: document.path,
            icon: FileTextIcon,
          }))
        : [];

    const personalTemplateItems: PromptItem[] =
      hasValidSearch && searchTemplatesData
        ? searchTemplatesData.map((template) => ({
            id: `personal-template-${template.path}`,
            label: template.title,
            path: template.path,
            icon: LayoutTemplateIcon,
          }))
        : [];

    const documentItems = [...filterBySearch(documentPageLinks), ...personalDocumentItems];

    const templateItems = [...filterBySearch(templatePageLinks), ...personalTemplateItems];

    const settingsItems = filterBySearch(settingsLinks);

    const allCategories: PromptCategory[] = [
      ...adminSearchCategories,
      {
        id: 'documents',
        label: msg`Documents`,
        items: documentItems,
        count: documentItems.length,
        chipCount: personalDocumentItems.length > 0 ? personalDocumentItems.length : null,
        isCapped: personalDocumentItems.length >= PERSONAL_SEARCH_RESULTS_CAP,
        isGlobal: false,
      },
      {
        id: 'templates',
        label: msg`Templates`,
        items: templateItems,
        count: templateItems.length,
        chipCount: personalTemplateItems.length > 0 ? personalTemplateItems.length : null,
        isCapped: personalTemplateItems.length >= PERSONAL_SEARCH_RESULTS_CAP,
        isGlobal: false,
      },
      {
        id: 'settings',
        label: msg`Settings`,
        items: settingsItems,
        count: settingsItems.length,
        chipCount: settingsItems.length,
        isCapped: false,
        isGlobal: false,
      },
    ];

    return allCategories.filter((category) => category.items.length > 0);
  }, [
    teamUrl,
    hasValidSearch,
    searchDocumentsData,
    searchTemplatesData,
    adminSearchCategories,
    filterBySearch,
    goToPage,
  ]);

  const effectiveChip = categories.some((category) => category.id === activeChip && category.chipCount !== null)
    ? activeChip
    : 'all';

  const visibleCategories =
    effectiveChip === 'all' ? categories : categories.filter((category) => category.id === effectiveChip);

  const totalVisibleCount = visibleCategories.reduce((total, category) => total + category.count, 0);
  const isVisibleCountCapped = visibleCategories.some((category) => category.isCapped);

  const totalAllCount = categories.reduce((total, category) => total + category.count, 0);
  const isAllCountCapped = categories.some((category) => category.isCapped);

  const isAnySearchFetching = isFetchingDocuments || isFetchingTemplates || isFetchingAdminSearch;

  const hasSearchError = isDocumentsSearchError || isTemplatesSearchError || isAdminSearchError;

  const formatChipCount = (count: number, isCapped: boolean) => (isCapped ? `≥${count}` : `${count}`);

  const goToSettings = useCallback(() => push('/settings/profile'), [push]);
  const goToDocuments = useCallback(() => {
    if (teamUrl) {
      push(`/t/${teamUrl}/documents?status=ALL`);
    }
  }, [push, teamUrl]);
  const goToTemplates = useCallback(() => {
    if (teamUrl) {
      push(`/t/${teamUrl}/templates`);
    }
  }, [push, teamUrl]);

  useHotkeys(['ctrl+k', 'meta+k'], toggleOpen, { preventDefault: true });
  useHotkeys(SETTINGS_PAGE_SHORTCUT, goToSettings);
  useHotkeys(DOCUMENTS_PAGE_SHORTCUT, goToDocuments);
  useHotkeys(TEMPLATES_PAGE_SHORTCUT, goToTemplates);

  const handleKeyDown = (event: React.KeyboardEvent) => {
    // Escape goes to the previous page, or closes the prompt at the root.
    // Backspace goes to the previous page when the search is empty.
    if (event.key === 'Escape' || (event.key === 'Backspace' && !search)) {
      event.preventDefault();

      if (activePage === null) {
        setOpen(false);
      }

      setActivePage(null);
    }
  };

  const isSearchLoading = isAnySearchFetching && hasValidSearch;

  const showSearchError = hasValidSearch && !isAnySearchFetching && hasSearchError;

  const showNoResults = hasValidSearch && totalVisibleCount === 0 && !isAnySearchFetching && !hasSearchError;

  const placeholder = match(activePage)
    .with('theme', () => msg`Search themes…`)
    .with('language', () => msg`Search languages…`)
    .otherwise(() => (isUserAdmin ? msg`Search documents, users, organisations…` : msg`Type a command or search...`));

  return (
    <Dialog open={isPromptOpen} onOpenChange={setOpen}>
      <DialogContent
        hideClose={true}
        overlayClassName="bg-foreground/40 backdrop-blur-[3px]"
        className="mt-[11vh] flex max-h-[min(560px,76vh)] w-[620px] max-w-[calc(100vw-2rem)] flex-col gap-0 self-start overflow-hidden rounded-[10px] p-0 shadow-2xl sm:max-w-[620px]"
      >
        <Command
          shouldFilter={false}
          loop={true}
          value={commandValue}
          onValueChange={setCommandValue}
          onKeyDown={handleKeyDown}
        >
          <div className="relative flex-none">
            <CommandInput
              value={search}
              onValueChange={setSearch}
              placeholder={_(placeholder)}
              className="h-14 pr-14 text-[15px]"
            />

            <button
              type="button"
              onClick={() => {
                // Mirror the escape key behaviour: back on sub pages,
                // otherwise close.
                if (activePage !== null) {
                  setActivePage(null);
                  return;
                }

                setOpen(false);
              }}
              className="absolute top-1/2 right-3 inline-flex -translate-y-1/2 items-center rounded border border-border bg-muted/50 px-1.5 py-0.5 font-mono text-[11px] text-muted-foreground"
            >
              <Trans>esc</Trans>
            </button>
          </div>

          {activePage === null && (
            <div className="flex flex-none gap-1.5 overflow-x-auto border-b px-3.5 py-2.5 [-ms-overflow-style:none] [scrollbar-width:none] [&::-webkit-scrollbar]:hidden">
              <PromptChip
                label={_(msg`All`)}
                count={formatChipCount(totalAllCount, isAllCountCapped)}
                isActive={effectiveChip === 'all'}
                onSelect={() => setActiveChip('all')}
              />

              {categories
                .filter((category) => category.chipCount !== null)
                .map((category) => (
                  <PromptChip
                    key={category.id}
                    label={_(category.label)}
                    count={formatChipCount(category.chipCount ?? 0, category.isCapped)}
                    isActive={effectiveChip === category.id}
                    isGlobal={category.isGlobal}
                    onSelect={() => setActiveChip(category.id)}
                  />
                ))}
            </div>
          )}

          <CommandList className="max-h-none flex-1 overscroll-contain px-2 pt-0.5 pb-2">
            {activePage === null && (
              <>
                {isSearchLoading && (
                  // The single loading state, replacing the results while any
                  // search is in flight. Mirrors the padding and content
                  // height of the no results state below so swapping between
                  // them doesn't change the height of the prompt.
                  <div className="flex items-center justify-center px-5 pt-12 pb-11">
                    <div className="flex h-11 items-center">
                      <LoaderIcon className="h-4 w-4 animate-spin text-muted-foreground" />
                    </div>
                  </div>
                )}

                {!isSearchLoading &&
                  visibleCategories.map((category) => (
                    <CommandGroup
                      key={category.id}
                      heading={
                        category.isGlobal ? (
                          <span className="inline-flex items-center gap-1.5">
                            <GlobeIcon aria-hidden="true" className="h-3 w-3" />
                            <span className="sr-only">{_(msg`Global`)} </span>
                            {_(category.label)}
                          </span>
                        ) : (
                          _(category.label)
                        )
                      }
                      className={PROMPT_GROUP_CLASSNAME}
                    >
                      {category.items.map((item) => (
                        <PromptCommandItem key={item.id} item={item} query={trimmedSearch} push={push} />
                      ))}
                    </CommandGroup>
                  ))}

                {showSearchError && totalVisibleCount > 0 && (
                  // Partial failure: the results from the searches that
                  // succeeded stay visible, flagged as incomplete.
                  <div className="px-5 pt-2 pb-3 text-center text-muted-foreground text-xs">
                    <Trans>Some searches failed — results may be incomplete.</Trans>
                  </div>
                )}

                {showSearchError && totalVisibleCount === 0 && (
                  // Total failure: an honest error state instead of a
                  // misleading "No results", height-matched to it so the
                  // prompt doesn't jump.
                  <div className="px-5 pt-12 pb-11 text-center">
                    <div className="font-medium text-sm">
                      <Trans>Something went wrong</Trans>
                    </div>
                    <div className="mt-1 text-muted-foreground text-sm">
                      <Trans>We couldn’t complete the search. Try again.</Trans>
                    </div>
                  </div>
                )}

                {showNoResults && (
                  <div className="px-5 pt-12 pb-11 text-center">
                    <div className="font-medium text-sm">
                      <Trans>No results for “{trimmedSearch}”</Trans>
                    </div>
                    <div className="mt-1 text-muted-foreground text-sm">
                      <Trans>Try a different search or switch category.</Trans>
                    </div>
                  </div>
                )}
              </>
            )}

            {activePage === 'theme' && (
              <PromptThemeCommands query={trimmedSearch} push={push} onBack={() => setActivePage(null)} />
            )}
            {activePage === 'language' && (
              <PromptLanguageCommands query={trimmedSearch} push={push} onBack={() => setActivePage(null)} />
            )}
          </CommandList>

          <div className="flex h-10 flex-none items-center justify-between gap-3 border-t bg-muted/50 px-3.5">
            <div className="hidden items-center gap-3.5 text-muted-foreground text-xs sm:flex">
              <span className="inline-flex items-center gap-1.5">
                <PromptKbd>↑</PromptKbd>
                <PromptKbd>↓</PromptKbd>
                <Trans>Navigate</Trans>
              </span>
              <span className="inline-flex items-center gap-1.5">
                <PromptKbd>↵</PromptKbd>
                <Trans>Open</Trans>
              </span>
              <span className="inline-flex items-center gap-1.5">
                <PromptKbd>esc</PromptKbd>
                {activePage === null ? <Trans>Close</Trans> : <Trans>Back</Trans>}
              </span>
            </div>

            <span className="ml-auto text-muted-foreground text-xs">
              {hasValidSearch ? (
                <Trans>{formatChipCount(totalVisibleCount, isVisibleCountCapped)} results</Trans>
              ) : (
                <Trans>{totalVisibleCount} items</Trans>
              )}
            </span>
          </div>
        </Command>
      </DialogContent>
    </Dialog>
  );
};

const PromptChip = ({
  label,
  count,
  isActive,
  isGlobal = false,
  onSelect,
}: {
  label: string;
  count: string;
  isActive: boolean;
  isGlobal?: boolean;
  onSelect: () => void;
}) => {
  const { _ } = useLingui();

  return (
    <button
      type="button"
      onClick={onSelect}
      aria-pressed={isActive}
      className={cn(
        'inline-flex h-7 flex-none items-center gap-1.5 whitespace-nowrap rounded-md border px-2.5 font-medium text-[13px] transition-colors',
        isActive
          ? 'border-input bg-muted text-foreground'
          : 'border-border bg-background text-muted-foreground hover:bg-muted/50',
      )}
    >
      {isGlobal && (
        <>
          <GlobeIcon aria-hidden="true" className="h-3.5 w-3.5" />
          <span className="sr-only">{_(msg`Global`)} </span>
        </>
      )}
      {label}
      <span className="font-normal opacity-60">{count}</span>
    </button>
  );
};

const PromptKbd = ({ children }: { children: React.ReactNode }) => {
  return (
    <span className="inline-flex h-[18px] min-w-[18px] items-center justify-center rounded border border-border bg-background px-1 font-mono text-[11px]">
      {children}
    </span>
  );
};

const HighlightedText = ({ text, query }: { text: string; query: string }) => {
  if (!query) {
    return <>{text}</>;
  }

  const index = text.toLowerCase().indexOf(query.toLowerCase());

  if (index === -1) {
    return <>{text}</>;
  }

  return (
    <>
      {text.slice(0, index)}
      <mark className="rounded-[3px] bg-primary/40 text-inherit">{text.slice(index, index + query.length)}</mark>
      {text.slice(index + query.length)}
    </>
  );
};

const PromptCommandItem = ({
  item,
  query,
  push,
  disabled = false,
}: {
  item: PromptItem;
  query: string;
  push: (_path: string) => void;
  disabled?: boolean;
}) => {
  const { _ } = useLingui();

  const label = typeof item.label === 'string' ? item.label : _(item.label);

  const onSelect = () => {
    if (item.onAction) {
      item.onAction();
      return;
    }

    if (item.path) {
      push(item.path);
    }
  };

  const content = (
    <>
      <span
        className={cn(
          'inline-flex h-[34px] w-[34px] flex-none items-center justify-center rounded-lg border border-border bg-background text-muted-foreground',
          item.initials && 'border-transparent bg-primary/10 font-semibold text-primary text-xs',
        )}
      >
        {item.initials ? item.initials : item.icon && <item.icon className="h-4 w-4" />}
      </span>

      <span className="min-w-0 flex-1">
        <span className="block truncate font-medium text-foreground text-sm">
          <HighlightedText text={label} query={query} />
        </span>
        {item.sublabel && (
          <span className="mt-px block truncate text-muted-foreground text-xs">
            <HighlightedText text={item.sublabel} query={query} />
          </span>
        )}
      </span>

      {item.shortcut && <span className="text-muted-foreground text-xs tracking-widest">{item.shortcut}</span>}

      {item.isChecked && <CheckIcon className="h-4 w-4 flex-none text-primary" />}

      <span className="hidden h-[22px] w-[22px] flex-none items-center justify-center rounded border border-border bg-background text-muted-foreground group-aria-selected:inline-flex">
        <CornerDownLeftIcon className="h-3 w-3" />
      </span>
    </>
  );

  return (
    <CommandItem
      value={item.id}
      onSelect={onSelect}
      disabled={disabled}
      className="group items-center gap-3 rounded-lg px-2.5 py-2"
    >
      {item.path ? (
        <Link
          to={item.path}
          tabIndex={-1}
          className="flex w-full min-w-0 items-center gap-3"
          onClick={(event) => {
            // Let the browser handle modified clicks natively, such as opening
            // the link in a new tab, without navigating or closing the prompt.
            if (event.metaKey || event.ctrlKey || event.shiftKey || event.altKey) {
              event.stopPropagation();
              return;
            }

            // Plain clicks bubble to the CommandItem which navigates and
            // closes the prompt via onSelect.
            event.preventDefault();
          }}
        >
          {content}
        </Link>
      ) : (
        content
      )}
    </CommandItem>
  );
};

const PromptBackCommand = ({ onBack }: { onBack: () => void }) => {
  return (
    <PromptCommandItem
      query=""
      push={() => undefined}
      item={{
        id: 'back',
        label: msg`Back`,
        icon: ArrowLeftIcon,
        onAction: onBack,
      }}
    />
  );
};

const PromptThemeCommands = ({
  query,
  push,
  onBack,
}: {
  query: string;
  push: (_path: string) => void;
  onBack: () => void;
}) => {
  const { _ } = useLingui();

  const [theme, setTheme, metadata] = useTheme();

  const themes = [
    { id: 'theme-light', label: msg`Light Mode`, icon: SunIcon, theme: Theme.LIGHT },
    { id: 'theme-dark', label: msg`Dark Mode`, icon: MoonIcon, theme: Theme.DARK },
    { id: 'theme-system', label: msg`System Theme`, icon: MonitorIcon, theme: null },
  ] as const;

  const visibleThemes = themes.filter((item) => !query || commandScore(_(item.label), query) >= MIN_FUZZY_SCORE);

  const isThemeChecked = (itemTheme: Theme | null) => {
    if (itemTheme === null) {
      return metadata.definedBy === 'SYSTEM';
    }

    return metadata.definedBy === 'USER' && theme === itemTheme;
  };

  return (
    <>
      <PromptBackCommand onBack={onBack} />

      <CommandGroup heading={_(msg`Theme`)} className={PROMPT_GROUP_CLASSNAME}>
        {visibleThemes.map((item) => (
          <PromptCommandItem
            key={item.id}
            query={query}
            push={push}
            item={{
              id: item.id,
              label: item.label,
              icon: item.icon,
              onAction: () => setTheme(item.theme),
              isChecked: isThemeChecked(item.theme),
            }}
          />
        ))}
      </CommandGroup>
    </>
  );
};

const PromptLanguageCommands = ({
  query,
  push,
  onBack,
}: {
  query: string;
  push: (_path: string) => void;
  onBack: () => void;
}) => {
  const { i18n, _ } = useLingui();
  const { toast } = useToast();

  const [isLoading, setIsLoading] = useState(false);

  const setLanguage = async (lang: string) => {
    if (isLoading || lang === i18n.locale) {
      return;
    }

    setIsLoading(true);

    try {
      await dynamicActivate(lang);

      const formData = new FormData();

      formData.append('lang', lang);

      const response = await fetch(formatPath('/api/locale'), {
        method: 'post',
        body: formData,
      });

      if (!response.ok) {
        throw new Error(response.statusText);
      }
    } catch (e) {
      console.error(`Failed to set language: ${e}`);

      toast({
        title: _(msg`An unknown error occurred`),
        variant: 'destructive',
        description: _(msg`Unable to change the language at this time. Please try again later.`),
      });
    }

    setIsLoading(false);
  };

  const visibleLanguages = Object.values(SUPPORTED_LANGUAGES).filter(
    (language) => !query || commandScore(_(language.full), query) >= MIN_FUZZY_SCORE,
  );

  return (
    <>
      <PromptBackCommand onBack={onBack} />

      <CommandGroup heading={_(msg`Language`)} className={PROMPT_GROUP_CLASSNAME}>
        {visibleLanguages.map((language) => (
          <PromptCommandItem
            key={language.short}
            query={query}
            push={push}
            disabled={isLoading}
            item={{
              id: `language-${language.short}`,
              label: language.full,
              initials: language.short.toUpperCase(),
              onAction: async () => setLanguage(language.short),
              isChecked: i18n.locale === language.short,
            }}
          />
        ))}
      </CommandGroup>
    </>
  );
};
