import { createContext, useContext, useEffect, useMemo, useState } from 'react';

import type { MappedAccount } from '~/lib/songwhipApi/accounts/types';
import type { Dispatch, FC, ReactNode, SetStateAction } from 'react';

import { getAccountApi } from '~/src/lib/songwhipApi/accounts';
import { accountConfigChangeAction } from '~/src/store/accounts/actions';
import { useDispatch } from '~/src/store/redux';

interface AccountContextValue {
  account: MappedAccount | undefined;
  accountError?: Error;
  accountIsLoading: boolean;

  setAccount: (account: MappedAccount) => void;
  setAccountIsLoading: Dispatch<SetStateAction<boolean>>;
  setAccountError: Dispatch<SetStateAction<Error | undefined>>;
}

const AccountContext = createContext<AccountContextValue>({} as any);

export const AccountProvider: FC<{
  accountId?: number;
  children: ReactNode;
}> = ({ accountId, children }) => {
  const [account, setAccount] = useState<MappedAccount>();
  const [isLoading, setIsLoading] = useState(true);
  const [error, setError] = useState<Error>();
  const dispatch = useDispatch();

  useEffect(() => {
    const fetchAccount = async (accountId: number) => {
      try {
        setIsLoading(true);
        setError(undefined);

        const result = await getAccountApi(accountId);

        setAccount(result);
      } catch (error) {
        setError(error);
      } finally {
        setIsLoading(false);
      }
    };

    // fetch account only in case it is not the same account or
    // there is an error from previous fetch
    if (accountId !== undefined && (accountId !== account?.id || error)) {
      fetchAccount(accountId);
    } else {
      setIsLoading(false);
    }
  }, [accountId]);

  return (
    <AccountContext.Provider
      value={useMemo<AccountContextValue>(
        () => ({
          account,
          accountError: error,
          accountIsLoading: isLoading,

          setAccount: (account) => {
            // Trigger a global redux action so the store can respond. In this
            // case we want to purge any Artist/Album/Track that have matching
            // ownedByAccounts[] as otherwise we may render stale content.
            dispatch(accountConfigChangeAction(account.id));

            setAccount(account);
            setIsLoading(false);
            setError(undefined);
          },

          setAccountIsLoading: setIsLoading,
          setAccountError: setError,
        }),
        [account, isLoading, error]
      )}
    >
      {children}
    </AccountContext.Provider>
  );
};

/**
 * A hook to fetch an Account and be able to update the
 * local Account state/reference when it changes.
 *
 * An alternative to this would be to store Accounts in redux,
 * but as they're only required for the AccountPage and not
 * used on others pages this doesn't have much value. Using
 * Redux would require more code and has potential for more bugs.
 */
const useAccount = () => useContext(AccountContext);

export default useAccount;
