import throttle from 'lodash/throttle';
import React from 'react';
import { defineMessages, useIntl } from 'react-intl';
import { useDispatch } from 'react-redux';
import { Link } from 'react-router-dom';

import { fetchOwnAccounts, logOut, switchAccount } from 'soapbox/actions/auth';
import Account from 'soapbox/components/account';
import { Menu, MenuButton, MenuDivider, MenuItem, MenuLink, MenuList } from 'soapbox/components/ui';
import { useAppSelector, useFeatures } from 'soapbox/hooks';
import { makeGetAccount } from 'soapbox/selectors';

import ThemeToggle from './theme-toggle';

import type { Account as AccountEntity } from 'soapbox/types/entities';

const messages = defineMessages({
  add: { id: 'profile_dropdown.add_account', defaultMessage: 'Add an existing account' },
  theme: { id: 'profile_dropdown.theme', defaultMessage: 'Theme' },
  logout: { id: 'profile_dropdown.logout', defaultMessage: 'Log out @{acct}' },
});

interface IProfileDropdown {
  account: AccountEntity
}

type IMenuItem = {
  text: string | React.ReactElement | null
  to?: string
  toggle?: JSX.Element
  icon?: string
  action?: (event: React.MouseEvent) => void
}

const getAccount = makeGetAccount();

const ProfileDropdown: React.FC<IProfileDropdown> = ({ account, children }) => {
  const dispatch = useDispatch();
  const features = useFeatures();
  const intl = useIntl();

  const authUsers = useAppSelector((state) => state.auth.get('users'));
  const otherAccounts = useAppSelector((state) => authUsers.map((authUser: any) => getAccount(state, authUser.get('id'))));

  const handleLogOut = () => {
    dispatch(logOut());
  };

  const handleSwitchAccount = (account: AccountEntity) => {
    return () => {
      dispatch(switchAccount(account.id));
    };
  };

  const fetchOwnAccountThrottled = throttle(() => {
    dispatch(fetchOwnAccounts());
  }, 2000);

  const renderAccount = (account: AccountEntity) => {
    return (
      <Account account={account} showProfileHoverCard={false} withLinkToProfile={false} hideActions />
    );
  };

  const menu: IMenuItem[] = React.useMemo(() => {
    const menu: IMenuItem[] = [];

    menu.push({ text: renderAccount(account), to: `/@${account.acct}` });

    otherAccounts.forEach((otherAccount: AccountEntity) => {
      if (otherAccount && otherAccount.id !== account.id) {
        menu.push({
          text: renderAccount(otherAccount),
          action: handleSwitchAccount(otherAccount),
        });
      }
    });

    menu.push({ text: null });
    menu.push({ text: intl.formatMessage(messages.theme), toggle: <ThemeToggle /> });
    menu.push({ text: null });

    menu.push({
      text: intl.formatMessage(messages.add),
      to: '/login/add',
      icon: require('@tabler/icons/plus.svg'),
    });

    menu.push({
      text: intl.formatMessage(messages.logout, { acct: account.acct }),
      to: '/logout',
      action: handleLogOut,
      icon: require('@tabler/icons/logout.svg'),
    });

    return menu;
  }, [account, authUsers, features]);

  React.useEffect(() => {
    fetchOwnAccountThrottled();
  }, [account, authUsers]);

  return (
    <Menu>
      <MenuButton>
        {children}
      </MenuButton>

      <MenuList>
        {menu.map((menuItem, idx) => {
          if (menuItem.toggle) {
            return (
              <div key={idx} className='flex flex-row items-center justify-between space-x-4 px-4 py-1 text-sm text-gray-700 dark:text-gray-400'>
                <span>{menuItem.text}</span>

                {menuItem.toggle}
              </div>
            );
          } else if (!menuItem.text) {
            return <MenuDivider key={idx} />;
          } else {
            const Comp: any = menuItem.action ? MenuItem : MenuLink;
            const itemProps = menuItem.action ? { onSelect: menuItem.action } : { to: menuItem.to, as: Link };

            return (
              <Comp key={idx} {...itemProps} className='truncate'>
                {menuItem.text}
              </Comp>
            );
          }
        })}
      </MenuList>
    </Menu>
  );
};

export default ProfileDropdown;
