import * as React from "react";
import PQueue from "p-queue";
import { useState, useCallback, useContext, useEffect, useRef, useMemo } from "@wordpress/element";
import type { Title, TitleIdType, TitleObjectType } from "../../types";
import JustwatchContext from "../../context/context";
import { AutoComplete, FetchSuggestionsOptions } from "../auto-complete/AutoComplete";
import { FetchResult, fetchTitleById, fetchTitlesByQuery } from "../../context/api";
import { isImdbId, getIdMatch, getId } from "../../context/api";
import { TitleInfo } from "../title-info/TitleInfo";
import { StyledSelectControl } from "./TitleSelect.styles";
import config from "../../context/config";
import { __, sprintf } from "@wordpress/i18n";

export type TitleIdDisplayType = TitleIdType | "abc";

export type TitleSelectProps<T extends Title> = {
  inputRef?: React.RefObject<HTMLInputElement>;
  selectedTitle?: T;
  label?: string;
  help?: React.JSX.Element;
  idType?: TitleIdType;
  highlight?: boolean;
  onTitleSelect?: (title: T) => void;
  onTitleData?: (title: T) => void;
};

type FilterOption = {
  label: string;
  value: string;
}

const filterOptions: FilterOption[] = [
  { value: "movie", label: "MOVIE" },
  { value: "show", label: "SHOW" },
  { value: "all", label: "ALL" },
].map(option => ({ ...option, value: option.value.toString() }));

type FilterType = "all" | "movie" | "show";

export const TitleSelect = <T extends Title>({
  selectedTitle: initialSelectedTitle,
  inputRef: inputRefProp,
  help,
  label,
  onTitleSelect,
  onTitleData,
}: TitleSelectProps<T>): JSX.Element => {
  const inputRef = inputRefProp || useRef<HTMLInputElement>(null);
  const [activeFilter, setActiveFilter] = useState<FilterType>("all");
  const [selectedTitle, setSelectedTitle] = useState<T | null>(initialSelectedTitle || null);
  const { apiKey, apiUrl } = useContext(JustwatchContext);
  const { locale, country } = config;
  const queue = useMemo(() => new PQueue({ concurrency: 1, intervalCap: 1, interval: 1000 }), []);
  const valueProp = 'title';
  const [changed, setChanged] = useState<boolean>(false);
  const [isFetched, setFetched] = useState<boolean>(false);

  const fetchSelectedTitle = useCallback(async() => {
    if (initialSelectedTitle) {
      const justwatchId = Number(getId(initialSelectedTitle, "justwatch"));
      const objectType = initialSelectedTitle.objectType;

      const isDifferentTitle = justwatchId !== selectedTitle?.justwatchId || objectType !== selectedTitle.objectType;
      const isPartialTitle = !selectedTitle?.title
        || !selectedTitle?.originalReleaseYear
        || selectedTitle.objectType === 'show' && !selectedTitle?.seasons;

      const needsUpdate = !isFetched && (isDifferentTitle || isPartialTitle);

      if (needsUpdate) {
        setFetched(true);
        const item = await fetchTitleById(justwatchId, objectType, { apiKey, apiUrl });

        if (item) {
          onTitleData?.(item as T);
          setSelectedTitle((prev) => {
            const isDifferentTitle = prev && (
              prev.justwatchId !== item.justwatchId
              || prev.objectType !== item.objectType);

            if (isDifferentTitle || isPartialTitle) {
              return item as T;
            }

            return prev;
          });
        }
      }
    }
  }, [initialSelectedTitle, selectedTitle]);

  useEffect(() => {
    fetchSelectedTitle();
  }, [fetchSelectedTitle]);

  useEffect(() => {
    if (selectedTitle) {
      setActiveFilter(selectedTitle.objectType);
      setChanged(false);
    }
  }, [selectedTitle]);

  const fetchSuggestions = useCallback(async(
      query: string,
      options: FetchSuggestionsOptions<T> = {}
    ): Promise<{ items: T[], totalResults: number }> => {
      queue.clear();
      const { page, signal } = {
        ...options,
      } as { page?: number; signal?: AbortSignal };

      const objectType = activeFilter === "all" ? undefined : activeFilter;

      if (!query.trim()) return Promise.resolve({ items: [], totalResults: 0 });

      const tasks: (() => Promise<FetchResult | Title | null>)[] = [];
      const idType = isImdbId(query) ? "imdb" : undefined;
      const fetchOptions = { apiKey, page, objectType: objectType as TitleObjectType, apiUrl };

      if (idType) {
        ["movie", "show"].forEach((objectType) => {
          tasks.push(
            () => fetchTitleById(query, objectType as TitleObjectType, { idType, ...fetchOptions })
              .catch((error) => {
                if (error.status !== 404) {
                  throw error;
                }

                return null;
              })
          );
        });
      } else {
        tasks.push(() => fetchTitlesByQuery(query, fetchOptions));
      }

      const results = await queue.addAll(tasks, { signal }) as Array<FetchResult | Title | null>;
      const mergedResults = results.reduce<FetchResult & { items: T[] }>((acc, result) => {
        if (!result) return acc;
        if ("items" in result) {
          if (Array.isArray(result.items)) {
            acc.items.push(...result.items);
          }
          acc.totalResults += typeof result.totalResults === "number" ? result.totalResults : 0;
        } else {
          acc.items.push(result as T);
          acc.totalResults++;
        }

        return acc;
      }, { items: [], totalResults: 0, page: 1, totalPages: 1 });

      if (idType) {
        mergedResults.items = mergedResults.items
          .filter((item: T) => getId(item, idType) === query) as T[];
      }
      // Filter duplicates
      mergedResults.items = (mergedResults.items.filter((item, index, self) =>
        item === self.find((t) => t.justwatchId === item.justwatchId
          && t.objectType === item.objectType
        )
      )) as T[];

      return mergedResults;
    }
  , [activeFilter, apiKey, apiUrl]);

  const handleSelect = (item: T) => {
    setSelectedTitle(item);
    onTitleSelect?.(item);
  };

  const renderSuggestion = (item: T, query: string) => {
    const idMatch = getIdMatch(item, query);

    return (
      <TitleInfo
        key={`${item.justwatchId}:${item.objectType}`}
        details={false}
        item={item}
        highlight={idMatch ? getId(item, idMatch.idType as TitleIdType) : query}
      />
    );
  }

  const handleFilterChange = (activeFilter: string | number) => {
    setActiveFilter(activeFilter as FilterType);
  };

  useEffect(() => {
    if (changed) {
      setActiveFilter("all");
    }
  }, [changed]);

  const suffix = (
    <StyledSelectControl
      role="combobox"
      value={activeFilter}
      onChange={(value: string | number) => handleFilterChange(value)}
      style={{ width: '60px', textAlign: 'right', marginBottom: 0 }}
      className="jw-title-select-filter"
    >
      {filterOptions.map((option) => (
        <option key={option.value} value={option.value}>
          {option.label}
        </option>
      ))}
    </StyledSelectControl>
  );

  const keyProvider = (item: T) => `${item.justwatchId}:${item.objectType}`;

  const handleBlur = () => {
    if (selectedTitle) {
      setActiveFilter(selectedTitle.objectType);
    }
  };

  const handleInput = () => {
    setChanged(true);
  };

  const noResultsMessage = country?.country && country?.fullLocale === locale
    ? __(sprintf("No results", country?.country), "justwatch-widgets") // (%s)
    : __("No results", "justwatch-widgets");

  return (
    <AutoComplete
      keyProvider={keyProvider}
      inputRef={inputRef}
      label={label}
      defaultValue={selectedTitle}
      onInput={handleInput}
      fetchSuggestions={fetchSuggestions}
      renderSuggestion={renderSuggestion}
      valueProp={valueProp}
      help={help}
      onItemChange={handleSelect}
      onBlur={handleBlur}
      suffix={suffix}
      noResultsMessage={noResultsMessage}
    />
  );
};