import { search } from "@simplybusiness/icons";
import { useCallback, useState } from "react";
import type { ComboboxOption } from "../Combobox";
import { Combobox } from "../Combobox";
import { Icon } from "../Icon";
import type {
  AddressLookupProps,
  AddressLookupService,
  LoqateSearchResponse,
  LoqateSearchResultItem,
} from "./types";
import { isLoqateErrorResponse } from "./utils";

function optionsFromResponse(
  { Items }: LoqateSearchResponse,
  addressLookupService: AddressLookupService,
) {
  if (!Items || !Array.isArray(Items)) {
    throw Error("No address found");
  }

  if (isLoqateErrorResponse(Items)) {
    throw Error(Items[0].Description);
  }

  return (Items as LoqateSearchResultItem[]).map(item => ({
    id: item.Id,
    label: `${item.Text}, ${item.Description}`,
    value: item.Text,
    // Add a callback to trigger secondary search
    // if the address type is not "Address"
    callback:
      item.Type === "Address"
        ? undefined
        : async () => {
            const result = await addressLookupService.findById(item.Id);
            return optionsFromResponse(result, addressLookupService);
          },
  }));
}

export const AddressLookup = ({
  ref,
  addressLookupService,
  onAddressSelected,
  onOptionsDisplayed,
  onError,
  errorMessage,
  ...otherProps
}: AddressLookupProps) => {
  const [error, _setError] = useState<Error | null>(null);

  const setError = useCallback(
    (newError: Error | null) => {
      if (newError != null) onError?.(newError);
      _setError(newError);
    },
    [onError],
  );

  const asyncOptions = useCallback(
    async (searchTerm: string) => {
      try {
        const response = await addressLookupService.search(searchTerm);
        setError(null);
        const options = optionsFromResponse(response, addressLookupService);
        onOptionsDisplayed?.();
        return options;
      } catch (e) {
        setError(e as Error);
        return [];
      }
    },
    [addressLookupService, setError, onOptionsDisplayed],
  );

  const handleSelected = (selected: ComboboxOption) => {
    setError(null);

    const isEmpty =
      typeof selected === "string"
        ? selected === ""
        : !selected.id && !selected.value;

    if (isEmpty) {
      return Promise.resolve();
    }

    if (typeof selected === "string" || !selected.id) {
      setError(new Error("Selected option missing id"));
      return Promise.resolve();
    }

    return addressLookupService
      .get(selected.id)
      .then(onAddressSelected)
      .catch(setError);
  };

  const realErrorMessage = (error && "An error occurred") || errorMessage;

  return (
    <Combobox
      {...otherProps}
      ref={ref}
      onSelected={handleSelected}
      asyncOptions={asyncOptions}
      errorMessage={realErrorMessage}
      icon={<Icon icon={search} />}
    />
  );
};

AddressLookup.displayName = "AddressLookup";
