import type { AvatarLayoutProps } from '../../avatarLayout';
import Button from '../../button';
import {
  SelectInput,
  SelectInputOptionContent,
  SelectInputTriggerButton,
} from '../../inputs/SelectInput';
import { CurrencyType, Props as ExpressiveMoneyInputProps } from '../ExpressiveMoneyInput';
import { ChevronDown } from '@transferwise/icons';
import { Flag } from '@wise/art';
import {
  type ButtonHTMLAttributes,
  forwardRef,
  type MouseEventHandler,
  useMemo,
  useState,
} from 'react';
import { useIntl } from 'react-intl';

import messages from '../ExpressiveMoneyInput.messages';
import { getLocaleCurrencyName } from '../../common';
import { formatCurrencyCode } from '@transferwise/formatting';

export interface CurrencyOption {
  label?: string;
  code: string;
  keywords: string[] | undefined;
}

export interface CurrencySection {
  title: string;
  currencies: CurrencyOption[];
}

export type CurrencyOptions = CurrencySection[];

export type Props = {
  id: string;
  labelId: string;
  options?: CurrencyOptions;
  onChange?: (currency: CurrencyType) => void;
  onOpen?: () => void;
  addons?: AvatarLayoutProps['avatars'];
  onSearchChange?: (payload: { query: string; resultCount: number }) => void;
} & Pick<ExpressiveMoneyInputProps, 'currency'>;

export const CurrencySelector = ({
  id,
  currency,
  options = [],
  labelId,
  onChange,
  addons,
  onOpen,
  onSearchChange,
}: Props) => {
  const intl = useIntl();

  const allCurrencyOptions = useMemo(() => getUniqueCurrencies(options), [options]);

  const activeCurrencyOption = useMemo(() => {
    // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
    return allCurrencyOptions.find((option) => option.code === currency)!;
  }, [currency, allCurrencyOptions]);

  const disabled =
    !onChange ||
    options.length === 0 ||
    (options.length === 1 && options[0].currencies.length <= 1);

  const [searchQuery, setSearchQuery] = useState<string>('');

  const handleTriggerClick: MouseEventHandler = (event) => {
    const triggerEl = event.currentTarget;
    if (triggerEl?.getAttribute('aria-expanded') === 'false') {
      onOpen?.();
    }
  };

  const items = searchQuery
    ? filterAndSortCurrenciesForQuery(allCurrencyOptions, searchQuery).map(getCurrencySelectOption)
    : options.map(getCurrencyGroup);

  return (
    <SelectInput
      compareValues="code"
      disabled={disabled}
      id={id}
      value={activeCurrencyOption}
      filterable
      filterPlaceholder={intl.formatMessage(messages.currencySelectorSearchPlaceholder)}
      UNSAFE_triggerButtonProps={{
        id: undefined,
        'aria-labelledby': undefined,
        'aria-describedby': labelId,
        'aria-invalid': undefined,
        'aria-label': intl.formatMessage(messages.currencySelectorSelectCurrency),
      }}
      items={items}
      renderValue={({ code, label }) => {
        return (
          <SelectInputOptionContent
            title={formatCurrencyCode(code)}
            note={label}
            icon={<Flag code={code} intrinsicSize={24} />}
          />
        );
      }}
      renderTrigger={() => (
        <SelectInputTriggerButton
          as={ButtonInput}
          // @ts-expect-error new (v2) ButtonProps
          addonStart={{
            type: 'avatar',
            value: [
              addons ? addons[0] : null,
              {
                ...(addons && addons.length > 1
                  ? { ...addons[1] }
                  : {
                      asset: <Flag code={currency} />,
                    }),
              },
            ]
              .filter(Boolean)
              .filter((avatar) => !(avatar && Object.keys(avatar).length === 0)),
          }}
          addonEnd={disabled ? undefined : { type: 'icon', value: <ChevronDown /> }}
          onClick={(event) => handleTriggerClick(event)}
        >
          <>
            <span aria-hidden>{formatCurrencyCode(currency)}</span>
            <span className="sr-only">{getLocaleCurrencyName(intl, currency)}</span>
          </>
        </SelectInputTriggerButton>
      )}
      onChange={(newValue) => {
        onChange?.(newValue.code);
      }}
      onFilterChange={({ queryNormalized }) => {
        setSearchQuery(queryNormalized ?? '');
        if (queryNormalized) {
          onSearchChange?.({
            query: queryNormalized,
            resultCount: filterAndSortCurrenciesForQuery(allCurrencyOptions, queryNormalized)
              .length,
          });
        }
      }}
    />
  );
};

export const ButtonInput = forwardRef(function ButtonInput(
  { children, ...rest }: React.PropsWithChildren<ButtonHTMLAttributes<HTMLButtonElement>>,
  ref: React.ForwardedRef<HTMLButtonElement | null>,
) {
  return (
    <Button
      ref={ref}
      size="md"
      v2
      className="wds-currency-selector"
      priority="secondary-neutral"
      {...rest}
    >
      {children}
    </Button>
  );
});

const getCurrencySelectOption = (currency: CurrencyOption) => {
  return {
    type: 'option' as const,
    value: currency,
    filterMatchers: currency.keywords,
  };
};

const getCurrencyGroup = (section: CurrencySection) => {
  return {
    type: 'group' as const,
    label: section.title,
    options: section.currencies.map(getCurrencySelectOption),
  };
};

const getUniqueCurrencies = (options: CurrencyOptions) => {
  const allCurrencyOptions = options.flatMap((section) => section.currencies);
  const uniqueCurrencies = new Map<string, CurrencyOption>();

  allCurrencyOptions.forEach((currencyObj) => {
    uniqueCurrencies.set(currencyObj.code, currencyObj);
  });

  return Array.from(uniqueCurrencies.values());
};

const filterAndSortCurrenciesForQuery = (
  currencies: CurrencyOption[],
  query: string,
): CurrencyOption[] => {
  return (
    currencies
      .filter((currency) => {
        return (
          formatCurrencyCode(currency.code).toLowerCase().includes(query) ||
          (currency.label ?? '').toLowerCase().includes(query) ||
          currency.keywords?.some((keyword) => keyword.toLowerCase().includes(query))
        );
      })
      // prefer exact matches, then sort alphabetically by code
      .sort((a, b) => {
        const aCode = a.code.toLowerCase();
        const bCode = b.code.toLowerCase();
        if (aCode === query) {
          return -1;
        }
        if (bCode === query) {
          return 1;
        }
        return aCode.localeCompare(bCode);
      })
  );
};
