import type { SearchItem, SearchResponse } from "./state";

import {
  setClearButtonState,
  clearResults,
  renderEmptyState,
  renderSearchErrorState,
  renderItems,
  appendItems,
} from "./render";
import {
  searchInput,
  searchResults,
  responseCache,
  getFetchTimeout,
  setFetchTimeout,
  setLastQuery,
  getLastQuery,
  getActiveRequestController,
  setActiveRequestController,
  setSearchPagination,
  getNextSearchPage,
  hasNextSearchPage,
  isFetchingNextPage,
  setFetchingNextPage,
} from "./state";

const parseSearchResponse = (payload: unknown): SearchResponse => {
  if (Array.isArray(payload)) {
    return { items: payload as SearchItem[], hasNextPage: false };
  }

  if (payload && typeof payload === "object" && Array.isArray((payload as SearchResponse).items)) {
    const response = payload as SearchResponse;
    return {
      items: response.items,
      hasNextPage: response.hasNextPage,
      nextPage: response.nextPage,
    };
  }

  return { items: [], hasNextPage: false };
};

const visibleSearchItems = (items: SearchItem[], query: string): SearchItem[] => {
  if (query === "") {
    return [];
  }

  return items.filter((item) => item.type === "anime");
};

const renderPendingQuery = (query: string): void => {
  if (!query) {
    return;
  }

  clearResults();
};

export const cancelScheduledFetch = (): void => {
  const fetchTimeout = getFetchTimeout();
  if (!fetchTimeout) {
    return;
  }

  window.clearTimeout(fetchTimeout);
  setFetchTimeout(undefined);
};

export const fetchSearchItems = (query: string): void => {
  setLastQuery(query);
  setClearButtonState(query !== "");

  const activeRequestController = getActiveRequestController();
  if (activeRequestController) {
    activeRequestController.abort();
    setActiveRequestController(undefined);
  }

  if (query === "") {
    clearResults();
    renderEmptyState("");
    return;
  }

  const cached = responseCache.get(query);
  if (cached) {
    setSearchPagination(cached.nextPage, cached.hasNextPage);
    renderItems(visibleSearchItems(cached.items, query));
    return;
  }

  renderPendingQuery(query);

  const controller = new AbortController();
  setActiveRequestController(controller);

  fetch(`/api/search?q=${encodeURIComponent(query)}`, { signal: controller.signal })
    .then((res: Response) => {
      if (!res.ok) {
        return { items: [], hasNextPage: false };
      }
      return res.json();
    })
    .then((payload: unknown) => {
      if (controller.signal.aborted || query !== getLastQuery()) {
        return;
      }

      const response = parseSearchResponse(payload);
      const visibleItems = visibleSearchItems(response.items, query);
      setActiveRequestController(undefined);
      setSearchPagination(response.nextPage, response.hasNextPage);
      responseCache.set(query, response);
      renderItems(visibleItems);
    })
    .catch((error) => {
      if (controller.signal.aborted) {
        return;
      }

      setActiveRequestController(undefined);
      console.error("search request failed:", error);
      renderSearchErrorState(query);
    });
};

const fetchNextSearchPage = (): void => {
  const query = getLastQuery();
  const page = getNextSearchPage();

  if (!query || !hasNextSearchPage() || !page || isFetchingNextPage()) {
    return;
  }

  setFetchingNextPage(true);

  fetch(`/api/search?q=${encodeURIComponent(query)}&page=${encodeURIComponent(String(page))}`)
    .then((res: Response) => {
      if (!res.ok) {
        return { items: [], hasNextPage: false };
      }
      return res.json();
    })
    .then((payload: unknown) => {
      if (query !== getLastQuery()) {
        return;
      }

      const response = parseSearchResponse(payload);
      const visibleItems = visibleSearchItems(response.items, query);
      const cached = responseCache.get(query);
      if (cached) {
        responseCache.set(query, {
          items: [...cached.items, ...response.items],
          hasNextPage: response.hasNextPage,
          nextPage: response.nextPage,
        });
      }
      setSearchPagination(response.nextPage, response.hasNextPage);
      appendItems(visibleItems);
    })
    .catch((error) => {
      window.showToast?.({ message: "Failed to load more search results." });
      console.error("failed to load more search results:", error);
    })
    .finally(() => {
      setFetchingNextPage(false);
    });
};

export const onResultsScroll = (): void => {
  if (!searchResults) {
    return;
  }

  const remainingScroll =
    searchResults.scrollHeight - searchResults.scrollTop - searchResults.clientHeight;
  if (remainingScroll < 480) {
    fetchNextSearchPage();
  }
};

export const scheduleFetch = (): void => {
  const fetchTimeout = getFetchTimeout();
  if (fetchTimeout) {
    window.clearTimeout(fetchTimeout);
  }

  const query = searchInput?.value.trim() || "";
  setClearButtonState(query !== "");
  setFetchTimeout(
    window.setTimeout(
      () => {
        fetchSearchItems(query);
      },
      query.length >= 2 ? 240 : 80,
    ),
  );
};
