import React, {
  useCallback,
  useEffect,
  useMemo,
  useRef,
  useState,
} from 'react';
import type { CSSProperties } from 'react';
import {
  Collection,
  Header,
  ListBox,
  ListBoxItem,
  ListBoxSection,
  ListLayout,
  Virtualizer,
} from 'react-aria-components';
import { Trans, useTranslation } from 'react-i18next';

import { Button } from '@actual-app/components/button';
import { AnimatedLoading } from '@actual-app/components/icons/AnimatedLoading';
import { SvgDelete } from '@actual-app/components/icons/v0';
import { SvgDotsHorizontalTriple } from '@actual-app/components/icons/v1';
import { Menu } from '@actual-app/components/menu';
import type { MenuItem, MenuItemObject } from '@actual-app/components/menu';
import { Popover } from '@actual-app/components/popover';
import { styles } from '@actual-app/components/styles';
import { Text } from '@actual-app/components/text';
import { theme } from '@actual-app/components/theme';
import { View } from '@actual-app/components/view';
import { validForMerge } from '@actual-app/core/shared/merge';
import * as monthUtils from '@actual-app/core/shared/months';
import { isPreviewId } from '@actual-app/core/shared/transactions';
import { validForTransfer } from '@actual-app/core/shared/transfer';
import { groupById, integerToCurrency } from '@actual-app/core/shared/util';
import type { IntegerAmount } from '@actual-app/core/shared/util';
import type {
  CategoryEntity,
  TransactionEntity,
} from '@actual-app/core/types/models';

import { FloatingActionBar } from '#components/mobile/FloatingActionBar';
import { useAccounts } from '#hooks/useAccounts';
import { useCategoriesById } from '#hooks/useCategories';
import { useLocale } from '#hooks/useLocale';
import { useNavigate } from '#hooks/useNavigate';
import { usePayees } from '#hooks/usePayees';
import { useScrollListener } from '#hooks/useScrollListener';
import { useSelectedDispatch, useSelectedItems } from '#hooks/useSelected';
import { useTransactionBatchActions } from '#hooks/useTransactionBatchActions';
import { useUndo } from '#hooks/useUndo';
import { setNotificationInset } from '#notifications/notificationsSlice';
import { useDispatch } from '#redux';

import { ROW_HEIGHT, TransactionListItem } from './TransactionListItem';

const NOTIFICATION_BOTTOM_INSET = 75;

type LoadingProps = {
  style?: CSSProperties;
  'aria-label': string;
};

function Loading({ style, 'aria-label': ariaLabel }: LoadingProps) {
  const { t } = useTranslation();
  return (
    <View
      aria-label={ariaLabel || t('Loading...')}
      style={{
        backgroundColor: theme.mobilePageBackground,
        flex: 1,
        justifyContent: 'center',
        alignItems: 'center',
        ...style,
      }}
    >
      <AnimatedLoading width={25} height={25} />
    </View>
  );
}

type TransactionListProps = {
  isLoading: boolean;
  transactions: readonly TransactionEntity[];
  showRunningBalances?: boolean;
  runningBalances?: Map<TransactionEntity['id'], IntegerAmount>;
  onOpenTransaction?: (transaction: TransactionEntity) => void;
  isLoadingMore: boolean;
  onLoadMore: () => void;
  showMakeTransfer?: boolean;
};

export function TransactionList({
  isLoading,
  transactions,
  showRunningBalances,
  runningBalances,
  onOpenTransaction,
  isLoadingMore,
  onLoadMore,
  showMakeTransfer = false,
}: TransactionListProps) {
  const locale = useLocale();
  const { t } = useTranslation();
  const sections = useMemo(() => {
    // Group by date. We can assume transactions is ordered
    const sections: {
      id: string;
      date: TransactionEntity['date'];
      transactions: TransactionEntity[];
    }[] = [];
    transactions.forEach(transaction => {
      if (
        sections.length === 0 ||
        transaction.date !== sections[sections.length - 1].date
      ) {
        sections.push({
          id: `${isPreviewId(transaction.id) ? 'preview/' : ''}${transaction.date}`,
          date: transaction.date,
          transactions: [],
        });
      }

      sections[sections.length - 1].transactions.push(transaction);
    });
    return sections;
  }, [transactions]);

  const dispatchSelected = useSelectedDispatch();
  const selectedTransactions = useSelectedItems();

  const onTransactionPress: (
    transaction: TransactionEntity,
    isLongPress?: boolean,
  ) => void = useCallback(
    (transaction, isLongPress = false) => {
      const isPreview = isPreviewId(transaction.id);
      if (!isPreview && (isLongPress || selectedTransactions.size > 0)) {
        dispatchSelected({ type: 'select', id: transaction.id });
      } else {
        onOpenTransaction?.(transaction);
      }
    },
    [dispatchSelected, onOpenTransaction, selectedTransactions],
  );

  useScrollListener(
    useCallback(
      ({ hasScrolledToEnd }) => {
        if (hasScrolledToEnd('down', 100)) {
          onLoadMore?.();
        }
      },
      [onLoadMore],
    ),
  );

  return (
    <View style={{ flex: 1 }}>
      {isLoading && (
        <Loading
          style={{ flex: 'none', paddingBottom: 8 }}
          aria-label={t('Loading transactions...')}
        />
      )}
      <View style={{ flex: 1 }}>
        <Virtualizer
          layout={ListLayout}
          layoutOptions={{
            estimatedRowHeight: ROW_HEIGHT,
            padding: 0,
          }}
        >
          <ListBox
            aria-label={t('Transaction list')}
            selectionMode={
              selectedTransactions.size > 0 ? 'multiple' : 'single'
            }
            style={{ flex: 1, overflow: 'auto' }}
            selectedKeys={selectedTransactions}
            dependencies={[
              selectedTransactions,
              locale,
              onTransactionPress,
              runningBalances,
              showRunningBalances,
              t,
            ]}
            renderEmptyState={() =>
              !isLoading && (
                <View
                  style={{
                    alignItems: 'center',
                    justifyContent: 'center',
                    backgroundColor: theme.mobilePageBackground,
                  }}
                >
                  <Text style={{ fontSize: 15 }}>
                    <Trans>No transactions</Trans>
                  </Text>
                </View>
              )
            }
            items={sections}
          >
            {section => (
              <ListBoxSection>
                <Header
                  style={{
                    ...styles.smallText,
                    backgroundColor: theme.pageBackground,
                    color: theme.tableHeaderText,
                    display: 'flex',
                    justifyContent: 'center',
                    paddingBottom: 4,
                    paddingTop: 4,
                    position: 'sticky',
                    top: '0',
                    width: '100%',
                    zIndex: 10,
                  }}
                >
                  {monthUtils.format(section.date, 'MMMM dd, yyyy', locale)}
                </Header>
                <Collection
                  items={section.transactions.filter(
                    t => !isPreviewId(t.id) || !t.is_child,
                  )}
                >
                  {transaction => (
                    <ListBoxItem textValue={transaction.id} value={transaction}>
                      {itemProps => (
                        <TransactionListItem
                          {...itemProps}
                          showRunningBalance={showRunningBalances}
                          runningBalance={runningBalances?.get(transaction.id)}
                          transaction={transaction}
                          onPress={trans => onTransactionPress(trans)}
                          onLongPress={trans => onTransactionPress(trans, true)}
                        />
                      )}
                    </ListBoxItem>
                  )}
                </Collection>
              </ListBoxSection>
            )}
          </ListBox>
        </Virtualizer>
      </View>

      {isLoadingMore && (
        <Loading
          aria-label={t('Loading more transactions...')}
          style={{
            // Same height as transaction list item
            height: ROW_HEIGHT,
          }}
        />
      )}

      {selectedTransactions.size > 0 && (
        <SelectedTransactionsFloatingActionBar
          transactions={transactions}
          showMakeTransfer={showMakeTransfer}
        />
      )}
    </View>
  );
}

type SelectedTransactionsFloatingActionBarProps = {
  transactions: readonly TransactionEntity[];
  style?: CSSProperties;
  showMakeTransfer: boolean;
};

function SelectedTransactionsFloatingActionBar({
  transactions,
  style = {},
  showMakeTransfer,
}: SelectedTransactionsFloatingActionBarProps) {
  const { t } = useTranslation();
  const editMenuTriggerRef = useRef(null);
  const [isEditMenuOpen, setIsEditMenuOpen] = useState(false);
  const moreOptionsMenuTriggerRef = useRef(null);
  const [isMoreOptionsMenuOpen, setIsMoreOptionsMenuOpen] = useState(false);
  const getMenuItemStyle = useCallback(
    <T extends string>(item: MenuItemObject<T>) => ({
      ...styles.mobileMenuItem,
      color: theme.mobileHeaderText,
      ...(item.disabled === true && { color: theme.buttonBareDisabledText }),
      ...(item.name === 'delete' && { color: theme.errorTextMenu }),
    }),
    [],
  );
  const selectedTransactions = useSelectedItems();
  const selectedTransactionsArray = Array.from(selectedTransactions);
  const dispatchSelected = useSelectedDispatch();

  const buttonProps = useMemo(
    () => ({
      style: {
        ...styles.mobileMenuItem,
        color: 'currentColor',
        height: styles.mobileMinHeight,
      },
      activeStyle: {
        color: 'currentColor',
      },
      hoveredStyle: {
        color: 'currentColor',
      },
    }),
    [],
  );

  const allTransactionsAreLinked = useMemo(() => {
    return transactions
      .filter(t => selectedTransactions.has(t.id))
      .every(t => t.schedule);
  }, [transactions, selectedTransactions]);

  const isMoreThanOne = selectedTransactions.size > 1;

  const { showUndoNotification } = useUndo();
  const {
    onBatchEdit,
    onBatchDuplicate,
    onBatchDelete,
    onBatchLinkSchedule,
    onBatchUnlinkSchedule,
    onSetTransfer,
    onMerge,
  } = useTransactionBatchActions();

  const navigate = useNavigate();
  const { data: accounts = [] } = useAccounts();
  const accountsById = useMemo(() => groupById(accounts), [accounts]);

  const { data: payees = [] } = usePayees();
  const payeesById = useMemo(() => groupById(payees), [payees]);

  const {
    data: { list: categoriesById } = {
      list: {} as Record<string, CategoryEntity>,
    },
  } = useCategoriesById();

  const dispatch = useDispatch();
  useEffect(() => {
    dispatch(
      setNotificationInset({ inset: { bottom: NOTIFICATION_BOTTOM_INSET } }),
    );
    return () => {
      dispatch(setNotificationInset(null));
    };
  }, [dispatch]);

  const twoTransactions: [TransactionEntity, TransactionEntity] | undefined =
    useMemo(() => {
      // only two selected
      if (selectedTransactionsArray.length !== 2) {
        return undefined;
      }

      const [a, b] = selectedTransactionsArray.map(id =>
        transactions.find(t => t.id === id),
      );
      if (!a || !b) {
        return undefined;
      }

      return [a, b];
    }, [selectedTransactionsArray, transactions]);

  const canBeTransfer = useMemo(() => {
    if (!twoTransactions) {
      return false;
    }
    const [fromTrans, toTrans] = twoTransactions;
    return validForTransfer(fromTrans, toTrans);
  }, [twoTransactions]);

  const canMerge = useMemo(() => {
    return Boolean(
      twoTransactions && validForMerge(twoTransactions[0], twoTransactions[1]),
    );
  }, [twoTransactions]);

  const moreOptionsMenuItems: MenuItem<string>[] = [
    {
      name: 'duplicate',
      text: t('Duplicate'),
    },
    {
      name: allTransactionsAreLinked ? 'unlink-schedule' : 'link-schedule',
      text: allTransactionsAreLinked
        ? t('Unlink schedule')
        : t('Link schedule'),
    },
    {
      name: 'delete',
      text: t('Delete'),
    },
    {
      name: 'merge',
      text: t('Merge'),
      disabled: !canMerge,
    },
  ];

  if (showMakeTransfer) {
    moreOptionsMenuItems.splice(2, 0, {
      name: 'transfer',
      text: t('Make transfer'),
      disabled: !canBeTransfer,
    });
  }

  return (
    <FloatingActionBar style={style}>
      <View
        style={{
          flex: 1,
          padding: 8,
          flexDirection: 'row',
          alignItems: 'center',
          justifyContent: 'space-between',
        }}
      >
        <View
          style={{
            flexDirection: 'row',
            alignItems: 'center',
            justifyContent: 'flex-start',
          }}
        >
          <Button
            variant="bare"
            {...buttonProps}
            style={{ ...buttonProps.style, marginRight: 4 }}
            onPress={() => {
              if (selectedTransactions.size > 0) {
                dispatchSelected({ type: 'select-none' });
              }
            }}
          >
            <SvgDelete width={10} height={10} />
          </Button>
          <Text style={styles.mediumText}>
            {selectedTransactions.size}{' '}
            {isMoreThanOne ? 'transactions' : 'transaction'} selected
          </Text>
        </View>
        <View
          style={{
            flexDirection: 'row',
            alignItems: 'center',
            justifyContent: 'flex-end',
            gap: 4,
          }}
        >
          <Button
            variant="bare"
            ref={editMenuTriggerRef}
            aria-label={t('Edit fields')}
            onPress={() => {
              setIsEditMenuOpen(true);
            }}
            {...buttonProps}
          >
            <Trans>Edit</Trans>
          </Button>

          <Popover
            triggerRef={editMenuTriggerRef}
            isOpen={isEditMenuOpen}
            onOpenChange={() => setIsEditMenuOpen(false)}
            style={{ width: 200 }}
          >
            <Menu
              getItemStyle={getMenuItemStyle}
              style={{ backgroundColor: theme.floatingActionBarBackground }}
              onMenuSelect={name => {
                void onBatchEdit?.({
                  name,
                  ids: selectedTransactionsArray,
                  onSuccess: (ids, name, value, mode) => {
                    let displayValue;
                    switch (name) {
                      case 'account':
                        displayValue =
                          accountsById[String(value)]?.name ?? value;
                        break;
                      case 'category':
                        displayValue =
                          categoriesById[String(value)]?.name ?? value;
                        break;
                      case 'payee':
                        displayValue = payeesById[String(value)]?.name ?? value;
                        break;
                      case 'amount':
                        displayValue = Number.isNaN(Number(value))
                          ? value
                          : integerToCurrency(Number(value));
                        break;
                      case 'notes':
                        displayValue = `${mode} with ${String(value)}`;
                        break;
                      default:
                        displayValue = value;
                        break;
                    }

                    showUndoNotification({
                      message: `Successfully updated ${name} of ${ids.length} transaction${ids.length > 1 ? 's' : ''} to [${String(displayValue)}](#${String(displayValue)}).`,
                      messageActions: {
                        [String(displayValue)]: () => {
                          switch (name) {
                            case 'account':
                              void navigate(`/accounts/${String(value)}`);
                              break;
                            case 'category':
                              void navigate(`/categories/${String(value)}`);
                              break;
                            case 'payee':
                              void navigate(`/payees`);
                              break;
                            default:
                              break;
                          }
                        },
                      },
                    });
                  },
                });
                setIsEditMenuOpen(false);
              }}
              items={[
                // Add support later on.
                // Pikaday doesn't play well will mobile.
                // We should consider switching to react-aria date picker.
                // {
                //   name: 'date',
                //   text: 'Date',
                // },
                {
                  name: 'account',
                  text: t('Account'),
                },
                {
                  name: 'payee',
                  text: t('Payee'),
                },
                {
                  name: 'notes',
                  text: t('Notes'),
                },
                {
                  name: 'category',
                  text: t('Category'),
                },
                // Add support later on until we have more user friendly amount input modal.
                // {
                //   name: 'amount',
                //   text: 'Amount',
                // },
                {
                  name: 'cleared',
                  text: t('Cleared'),
                },
              ]}
            />
          </Popover>

          <Button
            variant="bare"
            ref={moreOptionsMenuTriggerRef}
            aria-label={t('More options')}
            onPress={() => {
              setIsMoreOptionsMenuOpen(true);
            }}
            {...buttonProps}
          >
            <SvgDotsHorizontalTriple
              width={16}
              height={16}
              style={{ color: 'currentColor' }}
            />
          </Button>

          <Popover
            triggerRef={moreOptionsMenuTriggerRef}
            isOpen={isMoreOptionsMenuOpen}
            onOpenChange={() => setIsMoreOptionsMenuOpen(false)}
            style={{ width: 200 }}
          >
            <Menu
              getItemStyle={getMenuItemStyle}
              style={{ backgroundColor: theme.floatingActionBarBackground }}
              onMenuSelect={type => {
                if (type === 'duplicate') {
                  void onBatchDuplicate?.({
                    ids: selectedTransactionsArray,
                    onSuccess: ids => {
                      showUndoNotification({
                        message: t(
                          'Successfully duplicated {{count}} transactions.',
                          { count: ids.length },
                        ),
                      });
                    },
                  });
                } else if (type === 'link-schedule') {
                  void onBatchLinkSchedule?.({
                    ids: selectedTransactionsArray,
                    onSuccess: (ids, schedule) => {
                      // TODO: When schedule becomes available in mobile, update undo notification message
                      // with `messageActions` to open the schedule when the schedule name is clicked.
                      showUndoNotification({
                        message: t(
                          'Successfully linked {{count}} transactions to {{schedule}}.',
                          { count: ids.length, schedule: schedule.name },
                        ),
                      });
                    },
                  });
                } else if (type === 'unlink-schedule') {
                  void onBatchUnlinkSchedule?.({
                    ids: selectedTransactionsArray,
                    onSuccess: ids => {
                      showUndoNotification({
                        message: t(
                          'Successfully unlinked {{count}} transactions from their respective schedules.',
                          { count: ids.length },
                        ),
                      });
                    },
                  });
                } else if (type === 'delete') {
                  void onBatchDelete?.({
                    ids: selectedTransactionsArray,
                    onSuccess: ids => {
                      showUndoNotification({
                        type: 'warning',
                        message: t(
                          'Successfully deleted {{count}} transactions.',
                          { count: ids.length },
                        ),
                      });
                    },
                  });
                } else if (type === 'transfer') {
                  void onSetTransfer?.(selectedTransactionsArray, payees, ids =>
                    showUndoNotification({
                      message: t(
                        'Successfully marked {{count}} transactions as transfer.',
                        {
                          count: ids.length,
                        },
                      ),
                    }),
                  );
                } else if (type === 'merge') {
                  void onMerge?.(selectedTransactionsArray, () =>
                    showUndoNotification({
                      message: t('Successfully merged transactions'),
                    }),
                  );
                }
                setIsMoreOptionsMenuOpen(false);
              }}
              items={moreOptionsMenuItems}
            />
          </Popover>
        </View>
      </View>
    </FloatingActionBar>
  );
}
