import {
  createContext,
  useContext,
  useEffect,
  useMemo,
  useState,
  type PropsWithChildren,
} from 'react';
import { SearchCommandModal, type SearchKeywordOption } from '../../../components/search';
import { pushRecentSearchOption, resolveRecentSearchOptions } from '../../../app/main/searchRecent';
import type { SearchLayerSnapshot, SearchOpenMode, SearchWindowSelection, SearchWindowSelectionDraft } from '../types';

type SearchLayerContextValue = SearchLayerSnapshot & {
  setOptions: (options: SearchKeywordOption[]) => void;
  openSearch: (mode?: SearchOpenMode) => void;
  closeSearch: () => void;
  clearWindowSelection: (instanceId: string) => void;
  addWindowSelections: (selections: SearchWindowSelectionDraft[]) => void;
};

const SearchLayerContext = createContext<SearchLayerContextValue | null>(null);
const PREVIEW_APP_SELECTION_ID = 'page:preview:app';
const PLAY_APP_SELECTION_PREFIX = 'page:play:app:';

function isPlayAppSelectionId(selectionId: string) {
  return selectionId.startsWith(PLAY_APP_SELECTION_PREFIX);
}

function appendSelection(
  previous: SearchWindowSelection[],
  selection: SearchWindowSelectionDraft,
): SearchWindowSelection[] {
  const nextSelection: SearchWindowSelection = {
    ...selection,
    instanceId: `window-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,
  };

  if (selection.id === PREVIEW_APP_SELECTION_ID) {
    return [...previous.filter((item) => item.id !== PREVIEW_APP_SELECTION_ID), nextSelection];
  }

  if (isPlayAppSelectionId(selection.id)) {
    return [...previous.filter((item) => !isPlayAppSelectionId(item.id)), nextSelection];
  }

  return [...previous, nextSelection];
}

export function SearchLayerProvider({ children }: PropsWithChildren) {
  const [open, setOpen] = useState(false);
  const [options, setOptions] = useState<SearchKeywordOption[]>([]);
  const [mode, setMode] = useState<SearchOpenMode>('navigate');
  const [windowSelections, setWindowSelections] = useState<SearchWindowSelection[]>([]);
  const [recentOptions, setRecentOptions] = useState<SearchKeywordOption[]>([]);

  const appendWindowSelection = (option: SearchKeywordOption) => {
    setWindowSelections((previous) =>
      appendSelection(previous, {
        id: option.id,
        label: option.label,
        group: option.group,
        description: option.description,
        keywords: option.keywords ?? [],
      }),
    );
  };

  useEffect(() => {
    setRecentOptions(resolveRecentSearchOptions(options, mode));
  }, [mode, open, options]);

  const value = useMemo<SearchLayerContextValue>(
    () => ({
      open,
      options,
      mode,
      windowSelections,
      setOptions,
      openSearch: (nextMode = 'navigate') => {
        setMode(nextMode);
        setOpen(true);
      },
      closeSearch: () => {
        setOpen(false);
      },
      clearWindowSelection: (instanceId: string) => {
        setWindowSelections((previous) => previous.filter((selection) => selection.instanceId !== instanceId));
      },
      addWindowSelections: (selections) => {
        if (selections.length === 0) {
          return;
        }

        setWindowSelections((previous) => selections.reduce((result, selection) => appendSelection(result, selection), previous));
      },
    }),
    [mode, open, options, windowSelections],
  );

  return (
    <SearchLayerContext.Provider value={value}>
      {children}
      <SearchCommandModal
        open={open}
        options={options}
        recentOptions={recentOptions}
        mode={mode}
        onClose={value.closeSearch}
        onSelectOption={(option) => {
          pushRecentSearchOption(option.id, mode);
          setRecentOptions(resolveRecentSearchOptions(options, mode));

          if (mode === 'window' || option.id === PREVIEW_APP_SELECTION_ID) {
            setOpen(false);
            appendWindowSelection(option);
            (option.onSelectWindow ?? option.onSelect)();
            return;
          }

          option.onSelect();
        }}
      />
    </SearchLayerContext.Provider>
  );
}

export function useSearchLayerContext() {
  const context = useContext(SearchLayerContext);

  if (!context) {
    throw new Error('useSearchLayerContext must be used within a SearchLayerProvider.');
  }

  return context;
}
