import i18n from '@config/i18n';
import {getPersistedUserId} from '@services/localStorage';
import {asyncMap, compareUlid, getTime} from '@shared/utils';

import {
  Asset,
  BlockMessageReplied,
  DeliverStatus,
  HuDataMember,
  Message,
  MessageStatus,
} from '../interfaces';
import {getTextWithHighlights} from './mentions';
import {calculateTextHeight} from './measuring';
import {getDateFromULID} from './date';
import {getAssetsLabel} from './files';

const getFullName = (firstName?: string, lastName?: string): string =>
  `${firstName || ''} ${lastName || ''}`.trim();

export interface MessageAccessibilityParams {
  senderData: HuDataMember;
  isForwarded: boolean;
  replyBlock?: BlockMessageReplied;
  attachments?: Nullable<Asset[]>;
  text: string;
  ts: string;
  status?: Nullable<MessageStatus>;
  edited?: boolean;
  deliver_status?: DeliverStatus;
}

export const buildMessageAccessibilityLabel = ({
  senderData,
  isForwarded,
  replyBlock,
  attachments,
  text = '',
  ts,
  status,
  edited,
  deliver_status,
}: MessageAccessibilityParams): string => {
  const parts: string[] = [];
  const formattedTime = getTime(getDateFromULID(ts));
  const currentUser = getPersistedUserId();

  // Sender name
  const senderName =
    currentUser === senderData.id
      ? i18n.t('chat.you')
      : getFullName(senderData.first_name, senderData.last_name);
  parts.push(senderName);

  // Forwarded indicator
  if (isForwarded) {
    parts.push(i18n.t('chat.messages.forwarded'));
  }

  // Reply indicator
  if (replyBlock) {
    const replyMember = replyBlock.sender_membership?.user?.hu_data?.member;
    const replyToName = replyMember
      ? getFullName(replyMember.first_name, replyMember.last_name)
      : i18n.t('general.user');
    parts.push(`${i18n.t('chat.messages.reply')}: ${replyToName}`);
  }

  // Attachment label
  if (attachments?.length) {
    const {label} = getAssetsLabel(
      attachments.map(asset => ({
        format: asset.metadata.format,
        filename: asset.filename,
      })),
    );

    parts.push(label);
  }

  // Message text
  if (text) {
    parts.push(text);
  }

  // Time
  parts.push(formattedTime);

  // Message status. TODO: Add translations for these statuses.
  if (status === MessageStatus.Error) {
    parts.push(i18n.t('chat.errors.no_send_message'));
  } else if (status === MessageStatus.Sending) {
    parts.push(i18n.t('general.loading_send'));
  } else if (status === MessageStatus.Sent) {
    if (deliver_status?.received_at) {
      parts.push('Delivered');
    } else if (deliver_status?.read_at) {
      parts.push('Read');
    } else {
      parts.push('Sent');
    }
  }

  // Edited indicator
  if (edited) {
    parts.push(i18n.t('chat.edit.edited'));
  }

  return parts.join(', ');
};

export const getMessagesWithLocalData = async (
  messages: Message[],
): Promise<Message[]> => {
  const preparedMessages = await asyncMap(messages, async msg => {
    const formattedText = msg.formatted_text
      ? msg.formatted_text
      : msg.highlighted_spans?.length
      ? await getTextWithHighlights({
          message: msg.text,
          highlightedSpans: msg.highlighted_spans,
        })
      : msg.text;

    const message: Message = {
      ...msg,
      formatted_text: formattedText,
      height: await calculateTextHeight({
        text: formattedText,
        blocks: msg.blocks,
      }),
    };
    return message;
  });

  return preparedMessages;
};

export const sortMessages = (
  messagesWindow: Message[],
  pendingMessages: Message[],
) => {
  // Sort pending messages by ts (ascending order)
  const sortedPending = [...pendingMessages].sort((a, b) => {
    const aTs = a.client_msg_id || a.ts;
    const bTs = b.client_msg_id || b.ts;

    return compareUlid(aTs, bTs);
  });

  // Merge two sorted arrays efficiently using two pointers
  const mergedMessages: Message[] = [];
  let windowIndex = 0;
  let pendingIndex = 0;

  // Iterate through both arrays simultaneously
  while (
    windowIndex < messagesWindow.length &&
    pendingIndex < sortedPending.length
  ) {
    const windowMessage = messagesWindow[windowIndex];
    const pendingMessage = sortedPending[pendingIndex];

    // Compare timestamps to determine order
    if (
      compareUlid(
        windowMessage.client_msg_id || windowMessage.ts,
        pendingMessage.client_msg_id || pendingMessage.ts,
      ) <= 0
    ) {
      mergedMessages.push(windowMessage);
      windowIndex++;
    } else {
      mergedMessages.push(pendingMessage);
      pendingIndex++;
    }
  }

  // Add remaining messages from messagesWindow
  while (windowIndex < messagesWindow.length) {
    mergedMessages.push(messagesWindow[windowIndex]);
    windowIndex++;
  }

  // Add remaining messages from pendingMessages
  while (pendingIndex < sortedPending.length) {
    mergedMessages.push(sortedPending[pendingIndex]);
    pendingIndex++;
  }

  return mergedMessages;
};

export const buildDeletedMessageAccessibilityLabel = ({
  isSentByCurrentUser,
  senderName,
  ts,
}: {
  isSentByCurrentUser: boolean;
  senderName: string;
  ts: string;
}): string => {
  const parts: string[] = [];
  const formattedTime = getTime(getDateFromULID(ts));

  if (isSentByCurrentUser) {
    parts.push(i18n.t('chat.delete.message_deleted_you'));
  } else {
    parts.push(senderName);
    parts.push(i18n.t('chat.delete.message_deleted_user'));
  }

  parts.push(formattedTime);
  return parts.join(', ');
};

type ShortMessageData = Pick<Message, 'ts'>;

export const sortMessagesByTsHelper = (
  a: ShortMessageData,
  b: ShortMessageData,
) => {
  return compareUlid(a.ts, b.ts);
};

/**
 * Split an inclusive server response: extract the message at `anchorTs` (if
 * present) and return the rest. Used by paginated fetches that ask the
 * backend inclusively so callers can reconcile a possibly-stale local copy
 * of the anchor without polluting the page with a duplicate.
 */
export const splitAnchorFromMessages = (
  messages: Message[],
  anchorTs: string,
): {messages: Message[]; anchor?: Message} => {
  const anchor = messages.find(m => m.ts === anchorTs);
  return {
    anchor,
    messages: anchor ? messages.filter(m => m.ts !== anchorTs) : messages,
  };
};
