import { addActionHandler, getGlobal, setGlobal } from '../../index';

import type { ApiMessage } from '../../../api/types';
import { MAIN_THREAD_ID } from '../../../api/types';
import { FocusDirection } from '../../../types';

import {
  ANIMATION_END_DELAY,
  APP_VERSION,
  FAST_SMOOTH_MAX_DURATION,
  SERVICE_NOTIFICATIONS_USER_ID,
} from '../../../config';
import { IS_TOUCH_ENV } from '../../../util/environment';
import {
  enterMessageSelectMode,
  toggleMessageSelection,
  exitMessageSelectMode,
  replaceThreadParam,
  updateFocusDirection,
  updateFocusedMessage,
} from '../../reducers';
import {
  selectCurrentChat,
  selectViewportIds,
  selectIsRightColumnShown,
  selectCurrentMessageList,
  selectChat,
  selectThreadInfo,
  selectChatMessages,
  selectAllowedMessageActions,
  selectMessageIdsByGroupId,
  selectForwardedMessageIdsByGroupId,
  selectIsViewportNewest,
  selectReplyingToId,
  selectReplyStack,
  selectSender,
} from '../../selectors';
import { findLast } from '../../../util/iteratees';
import { getServerTime } from '../../../util/serverTime';

import versionNotification from '../../../versionNotification.txt';
import parseMessageInput from '../../../util/parseMessageInput';
import { getMessageSummaryText, getSenderTitle } from '../../helpers';
import * as langProvider from '../../../util/langProvider';
import { copyTextToClipboard } from '../../../util/clipboard';
import type { GlobalState } from '../../types';

const FOCUS_DURATION = 1500;
const FOCUS_NO_HIGHLIGHT_DURATION = FAST_SMOOTH_MAX_DURATION + ANIMATION_END_DELAY;
const POLL_RESULT_OPEN_DELAY_MS = 450;
const SERVICE_NOTIFICATIONS_MAX_AMOUNT = 1e3;

let blurTimeout: number | undefined;

addActionHandler('setScrollOffset', (global, actions, payload) => {
  const { chatId, threadId, scrollOffset } = payload!;

  return replaceThreadParam(global, chatId, threadId, 'scrollOffset', scrollOffset);
});

addActionHandler('setReplyingToId', (global, actions, payload) => {
  const { messageId } = payload!;
  const currentMessageList = selectCurrentMessageList(global);
  if (!currentMessageList) {
    return undefined;
  }
  const { chatId, threadId } = currentMessageList;

  return replaceThreadParam(global, chatId, threadId, 'replyingToId', messageId);
});

addActionHandler('setEditingId', (global, actions, payload) => {
  const { messageId } = payload!;
  const currentMessageList = selectCurrentMessageList(global);
  if (!currentMessageList) {
    return undefined;
  }

  const { chatId, threadId, type } = currentMessageList;
  const paramName = type === 'scheduled' ? 'editingScheduledId' : 'editingId';

  return replaceThreadParam(global, chatId, threadId, paramName, messageId);
});

addActionHandler('setEditingDraft', (global, actions, payload) => {
  const {
    text, chatId, threadId, type,
  } = payload;

  const paramName = type === 'scheduled' ? 'editingScheduledDraft' : 'editingDraft';

  return replaceThreadParam(global, chatId, threadId, paramName, text);
});

addActionHandler('editLastMessage', (global) => {
  const { chatId, threadId } = selectCurrentMessageList(global) || {};
  if (!chatId || !threadId) {
    return undefined;
  }

  const chatMessages = selectChatMessages(global, chatId);
  const viewportIds = selectViewportIds(global, chatId, threadId);
  if (!chatMessages || !viewportIds) {
    return undefined;
  }

  const lastOwnEditableMessageId = findLast(viewportIds, (id) => {
    return Boolean(chatMessages[id] && selectAllowedMessageActions(global, chatMessages[id], threadId).canEdit);
  });

  if (!lastOwnEditableMessageId) {
    return undefined;
  }

  return replaceThreadParam(global, chatId, threadId, 'editingId', lastOwnEditableMessageId);
});

addActionHandler('replyToNextMessage', (global, actions, payload) => {
  const { targetIndexDelta } = payload;
  const { chatId, threadId } = selectCurrentMessageList(global) || {};
  if (!chatId || !threadId) {
    return;
  }

  const chatMessages = selectChatMessages(global, chatId);
  const viewportIds = selectViewportIds(global, chatId, threadId);
  if (!chatMessages || !viewportIds) {
    return;
  }

  const replyingToId = selectReplyingToId(global, chatId, threadId);
  const isLatest = selectIsViewportNewest(global, chatId, threadId);

  let messageId: number | undefined;

  if (!isLatest || !replyingToId) {
    if (threadId === MAIN_THREAD_ID) {
      const chat = selectChat(global, chatId);

      messageId = chat?.lastMessage?.id;
    } else {
      const threadInfo = selectThreadInfo(global, chatId, threadId);

      messageId = threadInfo?.lastMessageId;
    }
  } else {
    const chatMessageKeys = Object.keys(chatMessages);
    const indexOfCurrent = chatMessageKeys.indexOf(replyingToId.toString());
    const newIndex = indexOfCurrent + targetIndexDelta;
    messageId = newIndex <= chatMessageKeys.length + 1 && newIndex >= 0
      ? Number(chatMessageKeys[newIndex])
      : undefined;
  }
  actions.setReplyingToId({ messageId });
  actions.focusMessage({
    chatId, threadId, messageId,
  });
});

addActionHandler('openAudioPlayer', (global, actions, payload) => {
  const {
    chatId, threadId, messageId, origin, volume, playbackRate, isMuted,
  } = payload;

  return {
    ...global,
    audioPlayer: {
      chatId,
      threadId,
      messageId,
      origin: origin ?? global.audioPlayer.origin,
      volume: volume ?? global.audioPlayer.volume,
      playbackRate: playbackRate || global.audioPlayer.playbackRate,
      isMuted: isMuted || global.audioPlayer.isMuted,
    },
  };
});

addActionHandler('setAudioPlayerVolume', (global, actions, payload) => {
  const {
    volume,
  } = payload;

  return {
    ...global,
    audioPlayer: {
      ...global.audioPlayer,
      volume,
      isMuted: false,
    },
  };
});

addActionHandler('setAudioPlayerPlaybackRate', (global, actions, payload) => {
  const {
    playbackRate,
  } = payload;

  return {
    ...global,
    audioPlayer: {
      ...global.audioPlayer,
      playbackRate,
    },
  };
});

addActionHandler('setAudioPlayerMuted', (global, actions, payload) => {
  const {
    isMuted,
  } = payload;

  return {
    ...global,
    audioPlayer: {
      ...global.audioPlayer,
      isMuted,
    },
  };
});

addActionHandler('setAudioPlayerOrigin', (global, actions, payload) => {
  const {
    origin,
  } = payload;

  return {
    ...global,
    audioPlayer: {
      ...global.audioPlayer,
      origin,
    },
  };
});

addActionHandler('closeAudioPlayer', (global) => {
  return {
    ...global,
    audioPlayer: {
      volume: global.audioPlayer.volume,
      playbackRate: global.audioPlayer.playbackRate,
      isMuted: global.audioPlayer.isMuted,
    },
  };
});

addActionHandler('openPollResults', (global, actions, payload) => {
  const { chatId, messageId } = payload!;

  const shouldOpenInstantly = selectIsRightColumnShown(global);

  if (!shouldOpenInstantly) {
    window.setTimeout(() => {
      global = getGlobal();

      setGlobal({
        ...global,
        pollResults: {
          chatId,
          messageId,
          voters: {},
        },
      });
    }, POLL_RESULT_OPEN_DELAY_MS);
  } else if (chatId !== global.pollResults.chatId || messageId !== global.pollResults.messageId) {
    return {
      ...global,
      pollResults: {
        chatId,
        messageId,
        voters: {},
      },
    };
  }

  return undefined;
});

addActionHandler('closePollResults', (global) => {
  return {
    ...global,
    pollResults: {},
  };
});

addActionHandler('focusLastMessage', (global, actions) => {
  const currentMessageList = selectCurrentMessageList(global);
  if (!currentMessageList) {
    return;
  }

  const { chatId, threadId } = currentMessageList;

  let lastMessageId: number | undefined;
  if (threadId === MAIN_THREAD_ID) {
    const chat = selectChat(global, chatId);

    lastMessageId = chat?.lastMessage?.id;
  } else {
    const threadInfo = selectThreadInfo(global, chatId, threadId);

    lastMessageId = threadInfo?.lastMessageId;
  }

  if (!lastMessageId) {
    return;
  }

  actions.focusMessage({
    chatId, threadId, messageId: lastMessageId, noHighlight: true,
  });
});

addActionHandler('focusNextReply', (global, actions) => {
  const currentMessageList = selectCurrentMessageList(global);
  if (!currentMessageList) {
    return undefined;
  }

  const { chatId, threadId } = currentMessageList;

  const replyStack = selectReplyStack(global, chatId, threadId);

  if (!replyStack || replyStack.length === 0) {
    actions.focusLastMessage();
  } else {
    const messageId = replyStack.pop();

    global = replaceThreadParam(global, chatId, threadId, 'replyStack', [...replyStack]);

    setGlobal(global);

    actions.focusMessage({
      chatId,
      threadId,
      messageId,
    });
  }

  return undefined;
});

addActionHandler('focusMessage', (global, actions, payload) => {
  const {
    chatId, threadId = MAIN_THREAD_ID, messageListType = 'thread', noHighlight, groupedId, groupedChatId,
    replyMessageId, isResizingContainer, shouldReplaceHistory,
  } = payload!;

  let { messageId } = payload!;

  if (groupedId !== undefined) {
    const ids = selectForwardedMessageIdsByGroupId(global, groupedChatId, groupedId);
    if (ids?.length) {
      ([messageId] = ids);
    }
  }

  const currentMessageList = selectCurrentMessageList(global);
  const shouldSwitchChat = !currentMessageList || (
    chatId !== currentMessageList.chatId
    || threadId !== currentMessageList.threadId
    || messageListType !== currentMessageList.type
  );

  if (blurTimeout) {
    clearTimeout(blurTimeout);
    blurTimeout = undefined;
  }
  blurTimeout = window.setTimeout(() => {
    let newGlobal = getGlobal();
    newGlobal = updateFocusedMessage(newGlobal);
    newGlobal = updateFocusDirection(newGlobal);
    setGlobal(newGlobal);
  }, noHighlight ? FOCUS_NO_HIGHLIGHT_DURATION : FOCUS_DURATION);

  global = updateFocusedMessage(global, chatId, messageId, noHighlight, isResizingContainer);
  global = updateFocusDirection(global, undefined);

  if (replyMessageId) {
    const replyStack = selectReplyStack(global, chatId, threadId) || [];
    global = replaceThreadParam(global, chatId, threadId, 'replyStack', [...replyStack, replyMessageId]);
  }

  if (shouldSwitchChat) {
    global = updateFocusDirection(global, FocusDirection.Static);
  }

  const viewportIds = selectViewportIds(global, chatId, threadId);
  if (viewportIds && viewportIds.includes(messageId)) {
    setGlobal(global);
    actions.openChat({ id: chatId, threadId, shouldReplaceHistory });
    return undefined;
  }

  if (shouldSwitchChat) {
    global = replaceThreadParam(global, chatId, threadId, 'viewportIds', undefined);
  }

  global = replaceThreadParam(global, chatId, threadId, 'outlyingIds', undefined);

  if (viewportIds && !shouldSwitchChat) {
    const direction = messageId > viewportIds[0] ? FocusDirection.Down : FocusDirection.Up;
    global = updateFocusDirection(global, direction);
  }

  setGlobal(global);

  actions.openChat({ id: chatId, threadId, shouldReplaceHistory });
  actions.loadViewportMessages();
  return undefined;
});

addActionHandler('openForwardMenu', (global, actions, payload) => {
  const {
    fromChatId, messageIds, groupedId, withMyScore,
  } = payload!;
  let groupedMessageIds;
  if (groupedId) {
    groupedMessageIds = selectMessageIdsByGroupId(global, fromChatId, groupedId);
  }
  return {
    ...global,
    forwardMessages: {
      fromChatId,
      messageIds: groupedMessageIds || messageIds,
      isModalShown: true,
      withMyScore,
    },
  };
});

addActionHandler('exitForwardMode', (global) => {
  setGlobal({
    ...global,
    forwardMessages: {},
  });
});

addActionHandler('setForwardChatId', (global, actions, payload) => {
  const { id } = payload!;

  setGlobal({
    ...global,
    forwardMessages: {
      ...global.forwardMessages,
      toChatId: id,
      isModalShown: false,
    },
  });

  actions.openChat({ id });
  actions.closeMediaViewer();
  actions.exitMessageSelectMode();
});

addActionHandler('openForwardMenuForSelectedMessages', (global, actions) => {
  if (!global.selectedMessages) {
    return;
  }

  const { chatId: fromChatId, messageIds } = global.selectedMessages;

  actions.openForwardMenu({ fromChatId, messageIds });
});

addActionHandler('cancelMessageMediaDownload', (global, actions, payload) => {
  const { message } = payload;

  const byChatId = global.activeDownloads.byChatId[message.chatId];
  if (!byChatId || !byChatId.length) return;

  setGlobal({
    ...global,
    activeDownloads: {
      byChatId: {
        ...global.activeDownloads.byChatId,
        [message.chatId]: byChatId.filter((id) => id !== message.id),
      },
    },
  });
});

addActionHandler('cancelMessagesMediaDownload', (global, actions, payload) => {
  const { messages } = payload;

  const byChatId = global.activeDownloads.byChatId;
  const newByChatId: GlobalState['activeDownloads']['byChatId'] = {};
  Object.keys(byChatId).forEach((chatId) => {
    newByChatId[chatId] = byChatId[chatId].filter((id) => !messages.find((message) => message.id === id));
  });
  return {
    ...global,
    activeDownloads: {
      byChatId: newByChatId,
    },
  };
});

addActionHandler('downloadMessageMedia', (global, actions, payload) => {
  const { message } = payload;

  setGlobal({
    ...global,
    activeDownloads: {
      byChatId: {
        ...global.activeDownloads.byChatId,
        [message.chatId]: [...(global.activeDownloads.byChatId[message.chatId] || []), message.id],
      },
    },
  });
});

addActionHandler('downloadSelectedMessages', (global, actions) => {
  if (!global.selectedMessages) {
    return;
  }

  const { chatId, messageIds } = global.selectedMessages;
  const { threadId } = selectCurrentMessageList(global) || {};

  const chatMessages = selectChatMessages(global, chatId);
  if (!chatMessages || !threadId) return;
  const messages = messageIds.map((id) => chatMessages[id])
    .filter((message) => selectAllowedMessageActions(global, message, threadId).canDownload);
  messages.forEach((message) => actions.downloadMessageMedia({ message }));
});

addActionHandler('enterMessageSelectMode', (global, actions, payload) => {
  const { messageId } = payload || {};
  const openChat = selectCurrentChat(global);
  if (!openChat) {
    return global;
  }

  return enterMessageSelectMode(global, openChat.id, messageId);
});

addActionHandler('toggleMessageSelection', (global, actions, payload) => {
  const {
    messageId,
    groupedId,
    childMessageIds,
    withShift,
  } = payload!;
  const currentMessageList = selectCurrentMessageList(global);
  if (!currentMessageList) {
    return;
  }

  const { chatId, threadId, type: messageListType } = currentMessageList;

  global = toggleMessageSelection(
    global, chatId, threadId, messageListType, messageId, groupedId, childMessageIds, withShift,
  );

  setGlobal(global);

  if (global.shouldShowContextMenuHint) {
    actions.disableContextMenuHint();
    actions.showNotification({
      // eslint-disable-next-line max-len
      message: `To **edit** or **reply**, close this menu. Then ${IS_TOUCH_ENV ? 'long tap' : 'right click'} on a message.`,
    });
  }
});

addActionHandler('disableContextMenuHint', (global) => {
  if (!global.shouldShowContextMenuHint) {
    return undefined;
  }

  return {
    ...global,
    shouldShowContextMenuHint: false,
  };
});

addActionHandler('exitMessageSelectMode', exitMessageSelectMode);

addActionHandler('openPollModal', (global, actions, payload) => {
  const { isQuiz } = payload || {};
  return {
    ...global,
    pollModal: {
      isOpen: true,
      isQuiz,
    },
  };
});

addActionHandler('closePollModal', (global) => {
  return {
    ...global,
    pollModal: {
      isOpen: false,
    },
  };
});

addActionHandler('checkVersionNotification', (global, actions) => {
  const currentVersion = APP_VERSION.split('.').slice(0, 2).join('.');
  const { serviceNotifications } = global;

  if (serviceNotifications.find(({ version }) => version === currentVersion)) {
    return;
  }

  const message: Omit<ApiMessage, 'id'> = {
    chatId: SERVICE_NOTIFICATIONS_USER_ID,
    date: getServerTime(global.serverTimeOffset),
    content: {
      text: parseMessageInput(versionNotification, true),
    },
    isOutgoing: false,
  };

  actions.createServiceNotification({
    message,
    version: currentVersion,
  });
});

addActionHandler('createServiceNotification', (global, actions, payload) => {
  const { message, version } = payload;
  const { serviceNotifications } = global;
  const serviceChat = selectChat(global, SERVICE_NOTIFICATIONS_USER_ID)!;

  const maxId = Math.max(
    serviceChat.lastMessage?.id || 0,
    ...serviceNotifications.map(({ id }) => id),
  );
  const fractionalPart = (serviceNotifications.length + 1) / SERVICE_NOTIFICATIONS_MAX_AMOUNT;
  // The fractional ID is made of the largest integer ID and an incremented fractional part
  const id = Math.floor(maxId) + fractionalPart;

  message.previousLocalId = message.id;
  message.id = id;

  const serviceNotification = {
    id,
    message,
    version,
    isUnread: true,
  };

  setGlobal({
    ...global,
    serviceNotifications: [
      ...serviceNotifications.slice(-SERVICE_NOTIFICATIONS_MAX_AMOUNT),
      serviceNotification,
    ],
  });

  actions.apiUpdate({
    '@type': 'newMessage',
    id: message.id,
    chatId: message.chatId,
    message,
  });
});

addActionHandler('openReactorListModal', (global, actions, payload) => {
  const { chatId, messageId } = payload!;

  return {
    ...global,
    reactorModal: { chatId, messageId },
  };
});

addActionHandler('closeReactorListModal', (global) => {
  return {
    ...global,
    reactorModal: undefined,
  };
});

addActionHandler('openSeenByModal', (global, actions, payload) => {
  const { chatId, messageId } = payload!;

  return {
    ...global,
    seenByModal: { chatId, messageId },
  };
});

addActionHandler('closeSeenByModal', (global) => {
  return {
    ...global,
    seenByModal: undefined,
  };
});

addActionHandler('copySelectedMessages', (global) => {
  if (!global.selectedMessages) {
    return;
  }

  const { chatId, messageIds } = global.selectedMessages;
  copyTextForMessages(global, chatId, messageIds);
});

addActionHandler('copyMessagesByIds', (global, actions, payload: { messageIds?: number[] }) => {
  const { messageIds } = payload;
  const chat = selectCurrentChat(global);
  if (!messageIds || messageIds.length === 0 || !chat) {
    return;
  }

  copyTextForMessages(global, chat.id, messageIds);
});

function copyTextForMessages(global: GlobalState, chatId: string, messageIds: number[]) {
  const { threadId } = selectCurrentMessageList(global) || {};
  const lang = langProvider.getTranslation;

  const chatMessages = selectChatMessages(global, chatId);
  if (!chatMessages || !threadId) return;
  const messages = messageIds
    .map((id) => chatMessages[id])
    .filter((message) => selectAllowedMessageActions(global, message, threadId).canCopy)
    .sort((message1, message2) => message1.id - message2.id);

  const result = messages.reduce((acc, message) => {
    const sender = selectSender(global, message);
    acc.push(`> ${sender ? getSenderTitle(lang, sender) : ''}:`);
    acc.push(`${getMessageSummaryText(lang, message, false, 0, undefined, true)}\n`);

    return acc;
  }, [] as string[]);

  copyTextToClipboard(result.join('\n'));
}
