import invariant from "invariant";
import { useEffect, useMemo, useState } from "react";
import cryptoFactory from "@ledgerhq/coin-cosmos/chain/chain";
import {
  getCosmosPreloadDataUpdates,
  getCurrentCosmosPreloadData,
} from "@ledgerhq/coin-cosmos/preloadedData";
import { getAccountCurrency } from "../../account";
import useMemoOnce from "../../hooks/useMemoOnce";
import { searchFilter as defaultSearchFilter, mapDelegations } from "./logic";
import type {
  CosmosAccount,
  CosmosDelegationInfo,
  CosmosMappedDelegation,
  CosmosMappedValidator,
  CosmosOperationMode,
  CosmosPreloadData,
  CosmosSearchFilter,
  CosmosValidatorItem,
  Transaction,
} from "./types";

export function useCosmosFamilyPreloadData(currencyId?: string): CosmosPreloadData {
  const getCurrent = getCurrentCosmosPreloadData;
  const getUpdates = getCosmosPreloadDataUpdates;

  const [state, setState] = useState(getCurrent);
  useEffect(() => {
    const sub = getUpdates().subscribe(setState);
    return () => sub.unsubscribe();
  }, [getCurrent, getUpdates]);
  return currencyId
    ? state[currencyId] ?? {
        validators: [], // NB initial state because UI need to work even if it's currently "loading", typically after clear cache
      }
    : {
        validators: [], // NB initial state because UI need to work even if it's currently "loading", typically after clear cache
      };
}

export function useCosmosFamilyMappedDelegations(
  account: CosmosAccount,
  mode?: CosmosOperationMode,
): CosmosMappedDelegation[] {
  const currencyId = account.currency.id;
  const { validators } = useCosmosFamilyPreloadData(currencyId);

  const delegations = account.cosmosResources?.delegations;
  invariant(delegations, "cosmos: delegations is required");
  const unit = getAccountCurrency(account).units[0];
  return useMemo(() => {
    const mappedDelegations = mapDelegations(delegations || [], validators, unit);
    return mode === "claimReward"
      ? mappedDelegations.filter(({ pendingRewards }) => pendingRewards.gt(0))
      : mappedDelegations;
  }, [delegations, validators, mode, unit]);
}

export function useCosmosFamilyDelegationsQuerySelector(
  account: CosmosAccount,
  transaction: Transaction,
  delegationSearchFilter: CosmosSearchFilter = defaultSearchFilter,
): {
  query: string;
  setQuery: (query: string) => void;
  options: CosmosMappedDelegation[];
  value: CosmosMappedDelegation | null | undefined;
} {
  const [query, setQuery] = useState<string>("");
  const delegations = useCosmosFamilyMappedDelegations(account, transaction.mode);
  const options = useMemo<CosmosMappedDelegation[]>(
    () => delegations.filter(delegationSearchFilter(query)),
    [query, delegations, delegationSearchFilter],
  );
  const selectedValidator = transaction.validators && transaction.validators[0];
  const value = useMemo(() => {
    switch (transaction.mode) {
      case "redelegate":
        invariant(transaction.sourceValidator, "cosmos: sourceValidator is required");
        return options.find(
          ({ validatorAddress }) => validatorAddress === transaction.sourceValidator,
        );

      default:
        return (
          selectedValidator &&
          delegations.find(({ validatorAddress }) => validatorAddress === selectedValidator.address)
        );
    }
  }, [delegations, selectedValidator, transaction, options]);
  return {
    query,
    setQuery,
    options,
    value,
  };
}

/** Hook to search and sort SR list according to initial votes and query */
export function useSortedValidators(
  search: string,
  validators: CosmosValidatorItem[],
  delegations: CosmosDelegationInfo[],
  validatorSearchFilter: CosmosSearchFilter = defaultSearchFilter,
): CosmosMappedValidator[] {
  const initialVotes = useMemoOnce(() => delegations.map(({ address }) => address));
  const mappedValidators = useMemo(
    () =>
      validators.map((validator, rank) => ({
        rank: rank + 1,
        validator,
      })),
    [validators],
  );
  const sortedVotes = useMemo(
    () =>
      mappedValidators
        .filter(({ validator }) => initialVotes.includes(validator.validatorAddress))
        .concat(
          mappedValidators.filter(
            ({ validator }) => !initialVotes.includes(validator.validatorAddress),
          ),
        ),
    [mappedValidators, initialVotes],
  );
  const sr = useMemo(
    () => (search ? mappedValidators.filter(validatorSearchFilter(search)) : sortedVotes),
    [search, mappedValidators, sortedVotes, validatorSearchFilter],
  );
  return sr;
}

export function useLedgerFirstShuffledValidatorsCosmosFamily(
  currencyId: string,
  searchInput?: string,
): CosmosValidatorItem[] {
  const data = getCurrentCosmosPreloadData()[currencyId];
  const ledgerValidatorAddress = cryptoFactory(currencyId).ledgerValidator;

  return useMemo(() => {
    return reorderValidators(data?.validators ?? [], ledgerValidatorAddress, searchInput);
  }, [data, ledgerValidatorAddress, searchInput]);
}

function reorderValidators(
  validators: CosmosValidatorItem[],
  ledgerValidatorAddress: string | undefined,
  searchInput?: string,
): CosmosValidatorItem[] {
  const sortedValidators = validators
    .filter(validator => validator.commission !== 1.0)
    .filter(validator =>
      searchInput ? validator.name.toLowerCase().includes(searchInput.toLowerCase()) : true,
    )
    .sort((a, b) => b.tokens - a.tokens);

  // move Ledger validator to the first position
  if (ledgerValidatorAddress) {
    const ledgerValidator = sortedValidators.find(
      v => v.validatorAddress === ledgerValidatorAddress,
    );

    if (ledgerValidator) {
      const sortedValidatorsLedgerFirst = sortedValidators.filter(
        v => v.validatorAddress !== ledgerValidatorAddress,
      );
      sortedValidatorsLedgerFirst.unshift(ledgerValidator);

      return sortedValidatorsLedgerFirst;
    }
  }

  return sortedValidators;
}
