import React, { Fragment, useRef, useState, useEffect, useCallback } from 'react';
import styled from 'styled-components';

import type { JSX, PointerEvent } from 'react';
import type { SearchFacetCount, SearchItemData } from '@redocly/theme/core/types';

import { SearchInput } from '@redocly/theme/components/Search/SearchInput';
import { SearchShortcut } from '@redocly/theme/components/Search/SearchShortcut';
import { Button } from '@redocly/theme/components/Button/Button';
import { breakpoints, concatClassNames } from '@redocly/theme/core/utils';
import { Portal } from '@redocly/theme/components/Portal/Portal';
import { SearchItem } from '@redocly/theme/components/Search/SearchItem';
import { SearchRecent } from '@redocly/theme/components/Search/SearchRecent';
import { SearchSuggestedPages } from '@redocly/theme/components/Search/SearchSuggestedPages';
import {
  useThemeHooks,
  useDialogHotKeys,
  useSearchFilter,
  useRecentSearches,
  useModalScrollLock,
} from '@redocly/theme/core/hooks';
import { Tag } from '@redocly/theme/components/Tag/Tag';
import { CloseIcon } from '@redocly/theme/icons/CloseIcon/CloseIcon';
import { SearchFilter } from '@redocly/theme/components/Search/SearchFilter';
import { SearchGroups } from '@redocly/theme/components/Search/SearchGroups';
import { Typography } from '@redocly/theme/components/Typography/Typography';
import { SpinnerLoader } from '@redocly/theme/components/Loaders/SpinnerLoader';
import { SearchAiDialog } from '@redocly/theme/components/Search/SearchAiDialog';
import { useSearchSession } from '@redocly/theme/core/contexts';
import { SettingsIcon } from '@redocly/theme/icons/SettingsIcon/SettingsIcon';
import { AiStarsIcon } from '@redocly/theme/icons/AiStarsIcon/AiStarsIcon';
import { ReturnKeyIcon } from '@redocly/theme/icons/ReturnKeyIcon/ReturnKeyIcon';
import { ChevronLeftIcon } from '@redocly/theme/icons/ChevronLeftIcon/ChevronLeftIcon';
import { EditIcon } from '@redocly/theme/icons/EditIcon/EditIcon';
import { AiStarsGradientIcon } from '@redocly/theme/icons/AiStarsGradientIcon/AiStarsGradientIcon';
import { ResetIcon } from '@redocly/theme/icons/ResetIcon/ResetIcon';
import { CloseFilledIcon } from '@redocly/theme/icons/CloseFilledIcon/CloseFilledIcon';

export type SearchDialogProps = {
  onClose: () => void;
  className?: string;
  initialMode?: 'search' | 'ai-dialog';
};

export function SearchDialog({
  onClose,
  className,
  initialMode = 'search',
}: SearchDialogProps): JSX.Element {
  const { useTranslate, useCurrentProduct, useSearch, useProducts, useAiSearch, useTelemetry } =
    useThemeHooks();
  const telemetry = useTelemetry();
  const { searchSessionId, refreshSearchSessionId } = useSearchSession();
  const products = useProducts();
  const currentProduct = useCurrentProduct();
  const [product, setProduct] = useState(currentProduct);
  const [mode, setMode] = useState<'search' | 'ai-dialog'>(initialMode);
  const autoSearchDisabled = mode !== 'search';
  const {
    query,
    setQuery,
    filter,
    setFilter,
    items,
    isSearchLoading,
    facets,
    setLoadMore,
    advancedSearch,
    askAi,
    groupField,
    searchError,
    retrySearch,
  } = useSearch(product?.name, autoSearchDisabled);
  const {
    isFilterOpen,
    onFilterToggle,
    onFilterChange,
    onFilterReset,
    onFacetReset,
    onQuickFilterReset,
  } = useSearchFilter(filter, setFilter);
  const { addSearchHistoryItem } = useRecentSearches();
  const aiSearch = useAiSearch({ filter: filter, product: product?.name });
  useModalScrollLock(true);
  const searchInputRef = useRef<HTMLInputElement>(null);
  const modalRef = useRef<HTMLDivElement>(null);

  const [isMobile, setIsMobile] = useState(false);

  useEffect(() => {
    const mediaQuery = window.matchMedia(`(max-width: ${breakpoints.small})`);
    setIsMobile(mediaQuery.matches);

    const handleChange = (e: MediaQueryListEvent) => {
      setIsMobile(e.matches);
    };
    mediaQuery.addEventListener('change', handleChange);
    return () => mediaQuery.removeEventListener('change', handleChange);
  }, []);

  const aiQueryRef = useRef<HTMLDivElement>(null);
  const firstSearchResultRef = useRef<HTMLAnchorElement>(null);
  const searchKeysWithResults = items ? Object.keys(items).filter((key) => items[key]?.length) : [];

  const { translate } = useTranslate();

  const handleClose = useCallback(() => {
    const value = searchInputRef?.current?.value;
    if (value) {
      addSearchHistoryItem(value);
    }

    // Refresh the search session id so a new session starts on next open
    refreshSearchSessionId();

    onClose();
  }, [addSearchHistoryItem, onClose, refreshSearchSessionId]);

  useDialogHotKeys(modalRef, handleClose);

  const focusSearchInput = () => {
    requestAnimationFrame(() => {
      searchInputRef.current?.focus();
    });
  };

  useEffect(() => {
    if (mode === 'ai-dialog' && aiSearch.isGeneratingResponse) {
      setQuery('');
    }
  }, [mode, aiSearch.isGeneratingResponse, setQuery]);

  useEffect(focusSearchInput, []);

  const handleOverlayClick = useCallback(
    (event: PointerEvent<HTMLElement>) => {
      if (event.button !== 0) return;
      const target = event.target as HTMLElement;
      if (typeof target.className !== 'string') return;
      if (target.className?.includes(' overlay')) {
        handleClose();
      }
    },
    [handleClose],
  );

  const mapItem = useCallback(
    (
      item: SearchItemData,
      index: number,
      results: SearchItemData[],
      innerRef?: React.Ref<HTMLAnchorElement>,
    ) => {
      let itemProduct;
      if (!product && item.document.product) {
        const folder = item.document.product?.folder;
        const resolvedProduct = products.find((product) =>
          product.slug.match(`/${folder.startsWith('./') ? folder.slice(2) : folder}/`),
        );
        itemProduct = resolvedProduct
          ? { name: resolvedProduct.name, icon: resolvedProduct.icon }
          : undefined;
      }

      return (
        <SearchItem
          key={`${index}-${item.document.id}`}
          item={item}
          product={itemProduct}
          innerRef={innerRef}
          onClick={() => {
            addSearchHistoryItem(query);
            telemetry.sendSearchResultClickedMessage([
              {
                object: 'search',
                query: query,
                url: item.document.url,
                totalResults: results.length.toString(),
                index: index.toString(),
                searchEngine: mode,
                searchSessionId,
              },
            ]);
            onClose();
          }}
        />
      );
    },
    [onClose, product, products, addSearchHistoryItem, query, telemetry, mode, searchSessionId],
  );

  const showLoadMore = useCallback(
    (groupKey: string, currentCount: number = 0) => {
      const groupFacet = facets.find((facet) => facet.field === groupField);
      let needLoadMore = false;
      if (groupFacet) {
        const groupValue = groupFacet.values.find((value) => {
          if (typeof value === 'object') {
            return value.value === groupKey;
          } else return false;
        }) as SearchFacetCount;
        needLoadMore = groupValue ? groupValue.count > currentCount : false;
      }
      return needLoadMore;
    },
    [facets, groupField],
  );

  const showResults = !!((filter && filter.length) || query);
  const showSearchFilterButton = advancedSearch && mode === 'search';
  const showAiSearchButton = askAi && mode === 'search';
  const showAiSearchItem = showAiSearchButton && query;
  const showHeaderButtons = showSearchFilterButton || showAiSearchButton;

  return (
    <Portal>
      <SearchOverlay
        data-component-name="Search/SearchDialog"
        ref={modalRef}
        onPointerDown={handleOverlayClick}
        className={concatClassNames('overlay', className)}
      >
        <SearchDialogWrapper className="scroll-lock" role="dialog">
          <SearchDialogHeader>
            {product && (
              <SearchProductTag color="product">
                {product.name}
                <CloseIcon onClick={() => setProduct(undefined)} color="--icon-color-additional" />
              </SearchProductTag>
            )}
            {mode === 'search' ? (
              <>
                <SearchInput
                  value={query}
                  onChange={setQuery}
                  placeholder={translate('search.label', 'Search docs...')}
                  isLoading={isSearchLoading}
                  inputRef={searchInputRef}
                  onSubmit={() => {
                    if (isSearchLoading) return;

                    if (showAiSearchButton && aiQueryRef.current) {
                      aiQueryRef.current.focus();
                    } else {
                      firstSearchResultRef.current?.focus();
                    }
                  }}
                  data-translation-key="search.label"
                />
                {showHeaderButtons && (
                  <SearchHeaderButtons>
                    {showAiSearchButton ? (
                      <SearchAiButton
                        icon={<AiStarsGradientIcon color="var(--search-ai-button-icon-color)" />}
                        onClick={() => {
                          setMode('ai-dialog');
                          if (query.trim()) {
                            aiSearch.askQuestion(query);
                          }
                          telemetry.sendSearchAiOpenedMessage([
                            {
                              object: 'search',
                              method: 'ai_search_button',
                            },
                          ]);
                        }}
                      >
                        {translate('search.ai.button', 'Search with AI')}
                      </SearchAiButton>
                    ) : null}
                    {showSearchFilterButton && (
                      <SearchFilterToggleButton icon={<SettingsIcon />} onClick={onFilterToggle} />
                    )}
                  </SearchHeaderButtons>
                )}
              </>
            ) : (
              <AiDialogHeaderWrapper>
                {initialMode === 'ai-dialog' ? (
                  <AiDialogHeaderTitle>
                    <AiStarsGradientIcon
                      color="var(--search-ai-button-icon-color)"
                      size="1.25rem"
                    />
                    {translate('search.ai.assistant', 'Assistant')}
                  </AiDialogHeaderTitle>
                ) : (
                  <Button
                    variant="secondary"
                    onClick={() => {
                      setMode('search');
                      aiSearch.clearConversation();
                      focusSearchInput();
                    }}
                    tabIndex={0}
                    icon={<ChevronLeftIcon />}
                  >
                    {isMobile
                      ? translate('search.ai.back', 'Back')
                      : translate('search.ai.backToSearch', 'Back to search')}
                  </Button>
                )}
                <AiDialogHeaderActionsWrapper>
                  <Button
                    variant="secondary"
                    disabled={!aiSearch.conversation.length}
                    onClick={() => {
                      refreshSearchSessionId();
                      aiSearch.clearConversation();
                    }}
                    tabIndex={0}
                    icon={<EditIcon />}
                  >
                    {translate('search.ai.newConversation', 'New conversation')}
                  </Button>
                  {isMobile && <Button variant="text" icon={<CloseIcon />} onClick={handleClose} />}
                </AiDialogHeaderActionsWrapper>
              </AiDialogHeaderWrapper>
            )}
          </SearchDialogHeader>

          <SearchDialogBody>
            {mode === 'search' ? (
              <>
                {advancedSearch && isFilterOpen && (
                  <SearchDialogBodyFilterView>
                    <SearchFilter
                      facets={facets}
                      filter={filter}
                      query={query}
                      quickFilterFields={[groupField]}
                      onFilterChange={onFilterChange}
                      onFilterReset={onFilterReset}
                      onFacetReset={onFacetReset}
                    />
                  </SearchDialogBodyFilterView>
                )}
                <SearchDialogBodyMainView>
                  <SearchGroups
                    facets={facets}
                    searchFilter={filter}
                    onFilterChange={onFilterChange}
                    onQuickFilterReset={onQuickFilterReset}
                    groupField={groupField}
                  />

                  {showAiSearchItem && (
                    <SearchWithAI
                      onClick={() => {
                        setMode('ai-dialog');
                        if (query.trim()) {
                          aiSearch.askQuestion(query);
                        }
                        telemetry.sendSearchAiOpenedMessage([
                          {
                            object: 'search',
                            method: 'ai_search_input',
                          },
                        ]);
                      }}
                      onKeyDown={(e) => {
                        if (e.key === 'Enter') {
                          setMode('ai-dialog');
                          if (query.trim()) {
                            aiSearch.askQuestion(query);
                          }
                          telemetry.sendSearchAiOpenedMessage([
                            {
                              object: 'search',
                              method: 'ai_search_input',
                            },
                          ]);
                        }
                      }}
                      ref={aiQueryRef}
                      tabIndex={0}
                      role="option"
                      aria-selected="true"
                    >
                      <AiStarsIcon
                        style={{ flexShrink: 0 }}
                        color="var(--search-ai-icon-color)"
                        size="36px"
                        background="var(--search-ai-icon-bg-color)"
                        margin="0 var(--spacing-md) 0 0"
                        borderRadius="var(--border-radius-lg)"
                      />
                      <QueryWrapper>
                        <Typography fontWeight="var(--font-weight-semibold)">{query}</Typography>
                      </QueryWrapper>
                      <Typography>- {translate('search.ai.label', 'Ask AI assistant')}</Typography>
                      <ReturnKeyIcon color="var(--search-item-text-color)" />
                    </SearchWithAI>
                  )}
                  {showResults ? (
                    searchKeysWithResults.length ? (
                      searchKeysWithResults.map((key, searchGroupKeyIdx) => {
                        const searchResultItems = items[key];

                        if (searchResultItems?.length) {
                          return (
                            <Fragment key={key}>
                              <SearchGroupTitle data-testid="search-group-title">
                                {key}
                              </SearchGroupTitle>
                              {searchResultItems.map((item, idx, resultsArr) =>
                                mapItem(
                                  item,
                                  idx,
                                  resultsArr,
                                  searchGroupKeyIdx === 0 ? firstSearchResultRef : undefined,
                                ),
                              )}
                              {showLoadMore(key, searchResultItems.length || 0) && (
                                <SearchGroupFooter
                                  tabIndex={0}
                                  data-translation-key="search.showMore"
                                  onKeyDown={(e) => {
                                    if (e.key === 'Enter') {
                                      setLoadMore({
                                        groupKey: key,
                                        offset: searchResultItems.length || 0,
                                      });
                                    }
                                  }}
                                  onClick={() =>
                                    setLoadMore({
                                      groupKey: key,
                                      offset: searchResultItems.length || 0,
                                    })
                                  }
                                >
                                  {translate('search.showMore', 'Show more')}
                                </SearchGroupFooter>
                              )}
                            </Fragment>
                          );
                        }

                        return null;
                      })
                    ) : isSearchLoading ? (
                      <SearchMessage data-translation-key="search.loading">
                        <SpinnerLoader size="26px" color="var(--search-input-icon-color)" />
                        {translate('search.loading', 'Loading...')}
                      </SearchMessage>
                    ) : searchError ? (
                      <SearchMessage data-translation-key="search.error">
                        <CloseFilledIcon size="21px" color="var(--color-error-base)" />
                        {translate('search.error.title', 'Oops! Something went wrong.')}
                        <Button
                          variant="secondary"
                          size="small"
                          onClick={retrySearch}
                          icon={<ResetIcon />}
                        >
                          {translate('search.error.button', 'Try again')}
                        </Button>
                      </SearchMessage>
                    ) : (
                      <SearchMessage data-translation-key="search.noResults">
                        <b>{translate('search.noResults.title', 'No results')}</b>
                      </SearchMessage>
                    )
                  ) : (
                    <>
                      <SearchRecent
                        onSelect={(query, index) => {
                          telemetry.sendSearchRecentClickedMessage([
                            {
                              object: 'search',
                              query,
                              index: index.toString(),
                              searchSessionId,
                            },
                          ]);
                          setQuery(query);
                          focusSearchInput();
                        }}
                      />
                      <SearchSuggestedPages />
                    </>
                  )}
                </SearchDialogBodyMainView>
              </>
            ) : (
              <SearchAiDialog
                initialMessage={query}
                response={aiSearch.response}
                isGeneratingResponse={aiSearch.isGeneratingResponse}
                error={aiSearch.error}
                resources={aiSearch.resources}
                conversation={aiSearch.conversation}
                setConversation={aiSearch.setConversation}
                onMessageSent={aiSearch.askQuestion}
                toolCalls={aiSearch.toolCalls}
                contentSegments={aiSearch.contentSegments}
              />
            )}
          </SearchDialogBody>
          <SearchDialogFooter>
            {mode === 'ai-dialog' ? (
              <AiDisclaimer>
                {translate(
                  'search.ai.disclaimer',
                  'AI search might provide incomplete or incorrect results. Verify important information.',
                )}
              </AiDisclaimer>
            ) : (
              <>
                <SearchShortcuts>
                  <SearchShortcut
                    data-translation-key="search.keys.navigate"
                    combination="Tab"
                    text={translate('search.keys.navigate', 'to navigate')}
                  />
                  <SearchShortcut
                    data-translation-key="search.keys.select"
                    combination="⏎"
                    text={translate('search.keys.select', 'to select')}
                  />
                  <SearchShortcut
                    data-translation-key="search.keys.exit"
                    combination="Esc"
                    text={translate('search.keys.exit', 'to exit')}
                  />
                </SearchShortcuts>
                {isSearchLoading && (
                  <SearchLoading>
                    <SpinnerLoader size="16px" color="var(--search-input-icon-color)" />
                    {translate('search.loading', 'Loading...')}
                  </SearchLoading>
                )}
                <SearchCancelButton
                  data-translation-key="search.cancel"
                  variant="secondary"
                  size="small"
                  onClick={handleClose}
                >
                  {translate('search.cancel', 'Cancel')}
                </SearchCancelButton>
              </>
            )}
          </SearchDialogFooter>
        </SearchDialogWrapper>
      </SearchOverlay>
    </Portal>
  );
}

const SearchOverlay = styled.div`
  position: fixed;
  display: flex;
  align-items: center;
  justify-content: center;
  top: 0;
  left: 0;
  width: 100vw;
  height: 100vh;
  background: var(--bg-color-modal-overlay);
  z-index: var(--z-index-overlay);

  @media screen and (max-width: ${breakpoints.small}) {
    align-items: start;
    position: fixed;
    overflow: hidden;
    overscroll-behavior: none;
  }
`;

const SearchDialogWrapper = styled.div`
  display: flex;
  flex-direction: column;
  overflow: auto;
  width: 100vw;
  height: 100vh;
  background: var(--search-modal-bg-color);
  box-shadow: var(--search-modal-box-shadow);
  border-radius: 0;

  @media screen and (max-width: ${breakpoints.small}) {
    min-height: -webkit-fill-available;
    min-height: 100dvh;
    height: 100dvh;
    width: 100vw;
  }

  @media screen and (min-width: ${breakpoints.small}) {
    border-radius: var(--search-modal-border-radius);
    width: var(--search-modal-width);
    min-height: var(--search-modal-min-height);
    min-width: var(--search-modal-min-width);
    max-width: 95vw;
    max-height: 95vh;
    height: var(--search-modal-min-height);
    resize: both;
  }
`;

const SearchDialogHeader = styled.header`
  display: flex;
  align-items: center;
  border-bottom: var(--search-modal-border);
  background-color: var(--search-modal-header-bg-color);
  padding: var(--search-modal-header-padding);
`;

const AiDialogHeaderWrapper = styled.div`
  display: flex;
  justify-content: space-between;
  align-items: center;
  width: 100%;
`;
const AiDialogHeaderActionsWrapper = styled.div`
  display: flex;
  gap: var(--spacing-xxs);
`;

const AiDialogHeaderTitle = styled.span`
  display: flex;
  align-items: center;
  gap: var(--spacing-xs);
  font-weight: var(--font-weight-semibold);
  font-size: var(--font-size-lg);
`;

const SearchDialogBody = styled.div`
  display: flex;
  flex-direction: row-reverse;
  flex: 1;
  min-height: 0;
  overflow: hidden;

  @media screen and (max-width: ${breakpoints.small}) {
    min-height: 0;
  }
`;

const SearchDialogBodyMainView = styled.div`
  flex: 2;
  flex-grow: 2;
  overflow-y: scroll;
  overscroll-behavior: contain;
  border-right: var(--search-modal-border);
`;

const SearchDialogBodyFilterView = styled.div`
  overflow: scroll;
  max-width: var(--search-filter-width);
  width: 100%;
`;

const SearchDialogFooter = styled.footer`
  display: flex;
  gap: var(--search-modal-footer-gap);
  padding: var(--search-modal-footer-padding);
  border-top: var(--search-modal-border);
`;

const SearchShortcuts = styled.div`
  display: none;
  justify-content: flex-start;
  align-items: center;
  gap: var(--search-shortcuts-gap);

  @media screen and (min-width: ${breakpoints.small}) {
    display: flex;
  }
`;

const SearchMessage = styled.div`
  display: flex;
  height: 40%;
  justify-content: center;
  align-items: center;
  flex-direction: column;
  font-size: var(--search-message-font-size);
  font-weight: var(--search-message-font-weight);
  line-height: var(--search-message-line-height);
  color: var(--search-message-text-color);
  gap: var(--search-message-gap);
`;

const SearchProductTag = styled(Tag)`
  --tag-border-radius: var(--border-radius);
  border: none;
  margin: var(--spacing-xs) var(--spacing-sm) !important;
`;

const SearchFilterToggleButton = styled(Button)`
  margin-left: 0;
`;

const SearchAiButton = styled(Button)`
  margin-left: 0;
`;

const SearchCancelButton = styled(Button)`
  width: 100%;

  @media screen and (min-width: ${breakpoints.small}) {
    display: none;
  }
`;

const SearchGroupTitle = styled.div`
  border-bottom: var(--search-modal-border);
  padding: var(--search-group-title-padding);
  background-color: var(--search-group-title-bg-color);
`;

const SearchGroupFooter = styled.div`
  display: flex;
  justify-content: center;
  padding: var(--search-group-footer-padding);
  color: var(--search-group-footer-text-color);
  cursor: pointer;
`;

const SearchLoading = styled.div`
  display: none;
  align-items: center;
  gap: var(--spacing-xs);

  @media screen and (min-width: ${breakpoints.small}) {
    display: flex;
  }
`;

const SearchHeaderButtons = styled.div`
  display: flex;
  gap: var(--search-header-buttons-gap);
  padding-left: var(--search-header-buttons-padding-left);
  border-left: var(--search-header-buttons-border-left);
`;

const AiDisclaimer = styled.div`
  font-size: var(--search-ai-disclaimer-font-size);
  line-height: var(--search-ai-disclaimer-line-height);
  color: var(--search-ai-disclaimer-text-color);
  margin: 0 auto;
  text-align: center;
`;

const SearchWithAI = styled.div`
  display: flex;
  justify-content: flex-start;
  align-items: center;
  cursor: pointer;
  gap: var(--spacing-unit);
  padding: var(--spacing-md);

  color: var(--search-item-text-color);
  background-color: var(--search-item-bg-color);
  text-decoration: none;
  white-space: normal;
  outline: none;
  border-top: 1px solid var(--search-item-border-color);
  border-bottom: 1px solid var(--search-item-border-color);

  transition: all 0.3s ease;

  ${ReturnKeyIcon} {
    opacity: 0;
  }

  &:focus,
  &:hover {
    color: var(--search-item-text-color-hover);
    background-color: var(--search-item-bg-color-hover);

    ${ReturnKeyIcon} {
      opacity: 1;
    }
  }

  &:focus {
    border-top: 1px solid var(--search-item-border-color-focused);
    border-bottom: 1px solid var(--search-item-border-color-focused);
  }

  & > :first-child {
    margin-right: var(--spacing-xs);
  }
`;

const QueryWrapper = styled.div`
  word-break: break-word;
`;
