import debounce from "lodash/debounce";
import { observer } from "mobx-react";
import * as React from "react";
import { useTranslation } from "react-i18next";
import styled from "styled-components";
import Empty from "~/components/Empty";
import { Outline } from "~/components/Input";
import InputSearch from "~/components/InputSearch";
import Placeholder from "~/components/List/Placeholder";
import PaginatedList from "~/components/PaginatedList";
import {
  Popover,
  PopoverAnchor,
  PopoverContent,
} from "~/components/primitives/Popover";
import { id as bodyContentId } from "~/components/SkipNavContent";
import useKeyDown from "~/hooks/useKeyDown";
import useStores from "~/hooks/useStores";
import type { SearchResult } from "~/types";
import SearchListItem from "./SearchListItem";

interface Props extends React.HTMLAttributes<HTMLInputElement> {
  shareId: string;
  className?: string;
}

function SearchPopover({ shareId, className }: Props) {
  const { t } = useTranslation();
  const { documents } = useStores();
  const focusRef = React.useRef<HTMLElement | null>(null);

  const [open, setOpen] = React.useState(false);
  const [query, setQuery] = React.useState("");

  const [searchResults, setSearchResults] = React.useState<
    SearchResult[] | undefined
  >();
  const [cachedQuery, setCachedQuery] = React.useState(query);
  const [cachedSearchResults, setCachedSearchResults] = React.useState<
    SearchResult[] | undefined
  >(searchResults);

  React.useEffect(() => {
    if (searchResults) {
      setCachedQuery(query);
      setCachedSearchResults(searchResults);
      setOpen(true);
    }
  }, [searchResults, query]);

  // Clear search results when the query changes to prevent stale results
  React.useEffect(() => {
    setSearchResults(undefined);
  }, [query]);

  const performSearch = React.useCallback(
    async ({ query: searchQuery, ...options }) => {
      if (searchQuery?.length > 0) {
        const response = await documents.search({
          query: searchQuery,
          shareId,
          ...options,
        });

        if (response.length) {
          setSearchResults((state) => [...(state ?? []), ...response]);
        }

        return response;
      }
      return undefined;
    },
    [documents, shareId]
  );

  const handleSearchInputChange = React.useMemo(
    () =>
      debounce(async (event: React.ChangeEvent<HTMLInputElement>) => {
        const { value } = event.target;
        const trimmedValue = value.trim();
        setQuery(trimmedValue);
        setOpen(!!trimmedValue);
      }, 300),
    [cachedQuery]
  );

  const searchInputRef = React.useRef<HTMLInputElement>(null);
  const firstSearchItem = React.useRef<HTMLAnchorElement>(null);

  const handleEscapeList = React.useCallback(
    () => searchInputRef?.current?.focus(),
    [searchInputRef]
  );

  const handleSearchInputFocus = React.useCallback(() => {
    focusRef.current = searchInputRef.current;
  }, [searchInputRef]);

  const handleKeyDown = React.useCallback(
    (ev: React.KeyboardEvent<HTMLInputElement>) => {
      if (ev.nativeEvent.isComposing) {
        return;
      }

      if (ev.key === "Enter") {
        if (searchResults) {
          setOpen(true);
        }
      }

      if (ev.key === "ArrowDown" && !ev.shiftKey) {
        if (ev.currentTarget.value.length) {
          const atEnd =
            ev.currentTarget.value.length === ev.currentTarget.selectionStart;

          if (atEnd) {
            setOpen(true);
          }

          if (open || atEnd) {
            ev.preventDefault();
            firstSearchItem.current?.focus();
          }
        }
      }

      if (ev.key === "ArrowUp") {
        if (open) {
          setOpen(false);
          if (!ev.shiftKey) {
            ev.preventDefault();
          }
        }

        if (ev.currentTarget.value) {
          if (ev.currentTarget.selectionEnd === 0) {
            ev.currentTarget.selectionStart = 0;
            ev.currentTarget.selectionEnd = ev.currentTarget.value.length;
            ev.preventDefault();
          }
        }
      }

      if (ev.key === "Escape") {
        if (open) {
          setOpen(false);
          ev.preventDefault();
        }
      }
    },
    [open, searchResults]
  );

  const handleSearchItemClick = React.useCallback(() => {
    setOpen(false);
    if (searchInputRef.current) {
      searchInputRef.current.value = "";
      focusRef.current = document.getElementById(bodyContentId);
    }
  }, [searchInputRef]);

  useKeyDown("/", (ev) => {
    if (
      searchInputRef.current &&
      searchInputRef.current !== document.activeElement
    ) {
      searchInputRef.current.focus();
      ev.preventDefault();
    }
  });

  return (
    <Popover open={open} onOpenChange={setOpen} modal={true}>
      <PopoverAnchor>
        <StyledInputSearch
          role="combobox"
          aria-controls="search-results"
          aria-expanded={open}
          aria-haspopup="listbox"
          ref={searchInputRef}
          onChange={handleSearchInputChange}
          onFocus={handleSearchInputFocus}
          onKeyDown={handleKeyDown}
          className={className}
          label={t("Search")}
          labelHidden
        />
      </PopoverAnchor>
      <PopoverContent
        id="search-results"
        aria-label={t("Results")}
        side="bottom"
        align="start"
        shrink
        onEscapeKeyDown={handleEscapeList}
        onOpenAutoFocus={(e) => e.preventDefault()}
        onInteractOutside={(event) => {
          const target = event.target as Element | null;
          if (target === searchInputRef.current) {
            event.preventDefault();
          }
        }}
      >
        <PaginatedList<SearchResult>
          role="listbox"
          options={{ query, snippetMinWords: 10, snippetMaxWords: 11 }}
          items={cachedSearchResults}
          fetch={performSearch}
          onEscape={handleEscapeList}
          empty={
            <NoResults>{t("No results for {{query}}", { query })}</NoResults>
          }
          loading={<PlaceholderList count={3} header={{ height: 20 }} />}
          renderItem={(item, index) => (
            <SearchListItem
              key={item.document.id}
              shareId={shareId}
              ref={index === 0 ? firstSearchItem : undefined}
              document={item.document}
              context={item.context}
              highlight={cachedQuery}
              onClick={handleSearchItemClick}
            />
          )}
        />
      </PopoverContent>
    </Popover>
  );
}

const NoResults = styled(Empty)`
  padding: 0 12px;
  margin: 6px 0;
`;

const PlaceholderList = styled(Placeholder)`
  padding: 6px 12px;
`;

const StyledInputSearch = styled(InputSearch)`
  ${Outline} {
    border-radius: 16px;
  }
`;

export default observer(SearchPopover);
