// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
import { getChatMessages, getIsLargeGroup, getLatestReadTime, getParticipants, getReadReceipts, getUserId } from './baseSelectors';
import { toFlatCommunicationIdentifier } from "../../acs-ui-common/src";
import { memoizeFnAll } from "../../acs-ui-common/src";
import { createSelector } from 'reselect';
import { ACSKnownMessageType } from './utils/constants';
import { updateMessagesWithAttached } from './utils/updateMessagesWithAttached';
const memoizedAllConvertChatMessage = memoizeFnAll((_key, chatMessage, userId, isSeen, isLargeGroup) => {
    const messageType = chatMessage.type.toLowerCase();
    if (messageType === ACSKnownMessageType.text || messageType === ACSKnownMessageType.richtextHtml || messageType === ACSKnownMessageType.html) {
        return convertToUiChatMessage(chatMessage, userId, isSeen, isLargeGroup);
    }
    else {
        return convertToUiSystemMessage(chatMessage);
    }
});
const extractAttachmentMetadata = (metadata) => {
    const attachmentMetadata = metadata.fileSharingMetadata;
    if (!attachmentMetadata) {
        return [];
    }
    try {
        return JSON.parse(attachmentMetadata);
    }
    catch (e) {
        console.error(e);
        return [];
    }
};
const extractTeamsAttachmentsMetadata = (rawAttachments) => {
    const attachments = [];
    rawAttachments.forEach(rawAttachment => {
        var _a;
        const attachmentType = rawAttachment.attachmentType;
        if (attachmentType === 'file') {
            attachments.push({
                id: rawAttachment.id,
                name: (_a = rawAttachment.name) !== null && _a !== void 0 ? _a : '',
                url: extractAttachmentUrl(rawAttachment)
            });
        }
    });
    return {
        attachments
    };
};
const extractAttachmentUrl = (attachment) => {
    return attachment.previewUrl ? attachment.previewUrl : attachment.url || '';
};
const processChatMessageContent = (message) => {
    var _a, _b, _c, _d;
    let content = (_a = message.content) === null || _a === void 0 ? void 0 : _a.message;
    if (((_b = message.content) === null || _b === void 0 ? void 0 : _b.attachments) && ((_c = message.content) === null || _c === void 0 ? void 0 : _c.attachments.length) > 0 && sanitizedMessageContentType(message.type).includes('html')) {
        const attachments = (_d = message.content) === null || _d === void 0 ? void 0 : _d.attachments;
        // Fill in the src here
        if (content) {
            const document = new DOMParser().parseFromString(content !== null && content !== void 0 ? content : '', 'text/html');
            document.querySelectorAll('img').forEach(img => {
                var _a, _b, _c;
                const attachmentPreviewUrl = (_a = attachments.find(attachment => attachment.id === img.id)) === null || _a === void 0 ? void 0 : _a.previewUrl;
                if (attachmentPreviewUrl) {
                    const resourceCache = (_b = message.resourceCache) === null || _b === void 0 ? void 0 : _b[attachmentPreviewUrl];
                    const src = getResourceSourceUrl(resourceCache);
                    // if in error state
                    if (src === undefined) {
                        const brokenImageView = getBrokenImageViewNode(img);
                        (_c = img.parentElement) === null || _c === void 0 ? void 0 : _c.replaceChild(brokenImageView, img);
                    }
                    else {
                        // else in loading or success state
                        img.setAttribute('src', src);
                    }
                    setImageWidthAndHeight(img);
                }
            });
            content = document.body.innerHTML;
        }
        const teamsImageHtmlContent = attachments.filter(attachment => { var _a, _b; return attachment.attachmentType === 'image' && attachment.previewUrl !== undefined && !((_b = (_a = message.content) === null || _a === void 0 ? void 0 : _a.message) === null || _b === void 0 ? void 0 : _b.includes(attachment.id)); }).map(attachment => generateImageAttachmentImgHtml(message, attachment)).join('');
        if (teamsImageHtmlContent) {
            return (content !== null && content !== void 0 ? content : '') + teamsImageHtmlContent;
        }
    }
    return content;
};
const generateImageAttachmentImgHtml = (message, attachment) => {
    var _a;
    if (attachment.previewUrl !== undefined) {
        const contentType = extractAttachmentContentTypeFromName(attachment.name);
        const resourceCache = (_a = message.resourceCache) === null || _a === void 0 ? void 0 : _a[attachment.previewUrl];
        const src = getResourceSourceUrl(resourceCache);
        // if in error state
        if (src === undefined) {
            return `\r\n<p>${getBrokenImageViewNode().outerHTML}</p>`;
        }
        // else in loading or success state
        return `\r\n<p><img alt="image" src="${src}" itemscope="${contentType}" id="${attachment.id}"></p>`;
    }
    return '';
};
const getResourceSourceUrl = (result) => {
    if (result) {
        if (!result.error && result.sourceUrl) {
            // return sourceUrl for success state
            return result.sourceUrl;
        }
        else {
            // return undefined for error state
            return undefined;
        }
    }
    // return empty string for loading state
    return '';
};
const extractAttachmentContentTypeFromName = (name) => {
    if (name === undefined) {
        return '';
    }
    const indexOfLastDot = name.lastIndexOf('.');
    if (indexOfLastDot === undefined || indexOfLastDot < 0) {
        return '';
    }
    const contentType = name.substring(indexOfLastDot + 1);
    return contentType;
};
const setImageWidthAndHeight = (img) => {
    if (img) {
        // define aspect ratio explicitly to prevent image not being displayed correctly
        // in safari, this includes image placeholder for loading state
        const width = img.width;
        const height = img.height;
        img.style.aspectRatio = `${width}/${height}`;
    }
};
const extractAttachmentsMetadata = (message) => {
    var _a, _b;
    let attachments = [];
    if (message.metadata) {
        attachments = attachments.concat(extractAttachmentMetadata(message.metadata));
    }
    if ((_a = message.content) === null || _a === void 0 ? void 0 : _a.attachments) {
        const teamsAttachments = extractTeamsAttachmentsMetadata((_b = message.content) === null || _b === void 0 ? void 0 : _b.attachments);
        attachments = attachments.concat(teamsAttachments.attachments);
    }
    return {
        attachments: attachments.length > 0 ? attachments : undefined
    };
};
const convertToUiChatMessage = (message, userId, isSeen, isLargeGroup) => {
    const messageSenderId = message.sender !== undefined ? toFlatCommunicationIdentifier(message.sender) : userId;
    const { attachments } = extractAttachmentsMetadata(message);
    return {
        messageType: 'chat',
        createdOn: message.createdOn,
        content: processChatMessageContent(message),
        contentType: sanitizedMessageContentType(message.type),
        status: !isLargeGroup && message.status === 'delivered' && isSeen ? 'seen' : message.status,
        senderDisplayName: message.senderDisplayName,
        senderId: messageSenderId,
        messageId: message.id,
        clientMessageId: message.clientMessageId,
        editedOn: message.editedOn,
        deletedOn: message.deletedOn,
        mine: messageSenderId === userId,
        metadata: message.metadata,
        attachments
    };
};
const convertToUiSystemMessage = (message) => {
    var _a, _b, _c, _d, _e;
    const systemMessageType = message.type;
    if (systemMessageType === 'participantAdded' || systemMessageType === 'participantRemoved') {
        return {
            messageType: 'system',
            systemMessageType,
            createdOn: message.createdOn,
            participants: (_c = (_b = (_a = message.content) === null || _a === void 0 ? void 0 : _a.participants) === null || _b === void 0 ? void 0 : _b.filter((participant) => participant.displayName && participant.displayName !== '').map((participant) => ({
                userId: toFlatCommunicationIdentifier(participant.id),
                displayName: participant.displayName
            }))) !== null && _c !== void 0 ? _c : [],
            messageId: message.id,
            iconName: systemMessageType === 'participantAdded' ? 'PeopleAdd' : 'PeopleBlock'
        };
    }
    else {
        // Only topic updated type left, according to ACSKnown type
        return {
            messageType: 'system',
            systemMessageType: 'topicUpdated',
            createdOn: message.createdOn,
            topic: (_e = (_d = message.content) === null || _d === void 0 ? void 0 : _d.topic) !== null && _e !== void 0 ? _e : '',
            messageId: message.id,
            iconName: 'Edit'
        };
    }
};
/** Returns `true` if the message has participants and at least one participant has a display name. */
const hasValidParticipant = (chatMessage) => { var _a; return !!((_a = chatMessage.content) === null || _a === void 0 ? void 0 : _a.participants) && chatMessage.content.participants.some((p) => !!p.displayName); };
/**
 *
 * @private
 */
export const messageThreadSelectorWithThread = () => createSelector([getUserId, getChatMessages, getLatestReadTime, getIsLargeGroup, getReadReceipts, getParticipants], (userId, chatMessages, latestReadTime, isLargeGroup, readReceipts = [], participants) => {
    // We can't get displayName in teams meeting interop for now, disable rr feature when it is teams interop
    const isTeamsInterop = Object.values(participants).find(p => 'microsoftTeamsUserId' in p.id) !== undefined;
    // get number of participants
    // filter out the non valid participants (no display name)
    // Read Receipt details will be disabled when participant count is 0
    const participantCount = isTeamsInterop ? undefined : Object.values(participants).filter(p => p.displayName && p.displayName !== '').length;
    // creating key value pairs of senderID: last read message information
    const readReceiptsBySenderId = {};
    // readReceiptsBySenderId[senderID] gets updated every time a new message is read by this sender
    // in this way we can make sure that we are only saving the latest read message id and read on time for each sender
    readReceipts.filter(r => r.sender && toFlatCommunicationIdentifier(r.sender) !== userId).forEach(r => {
        var _a, _b;
        readReceiptsBySenderId[toFlatCommunicationIdentifier(r.sender)] = {
            lastReadMessage: r.chatMessageId,
            displayName: (_b = (_a = participants[toFlatCommunicationIdentifier(r.sender)]) === null || _a === void 0 ? void 0 : _a.displayName) !== null && _b !== void 0 ? _b : ''
        };
    });
    // A function takes parameter above and generate return value
    const convertedMessages = memoizedAllConvertChatMessage(memoizedFn => Object.values(chatMessages).filter(message => message.type.toLowerCase() === ACSKnownMessageType.text || message.type.toLowerCase() === ACSKnownMessageType.richtextHtml || message.type.toLowerCase() === ACSKnownMessageType.html || message.type === ACSKnownMessageType.participantAdded && hasValidParticipant(message) || message.type === ACSKnownMessageType.participantRemoved && hasValidParticipant(message) ||
        // TODO: Add support for topicUpdated system messages in MessageThread component.
        // message.type === ACSKnownMessageType.topicUpdated ||
        message.clientMessageId !== undefined).filter(isMessageValidToRender).map(message => {
        var _a;
        return memoizedFn((_a = message.id) !== null && _a !== void 0 ? _a : message.clientMessageId, message, userId, message.createdOn <= latestReadTime, isLargeGroup);
    }));
    updateMessagesWithAttached(convertedMessages);
    return {
        userId,
        showMessageStatus: true,
        messages: convertedMessages,
        participantCount,
        readReceiptsBySenderId
    };
});
const sanitizedMessageContentType = (type) => {
    const lowerCaseType = type.toLowerCase();
    return lowerCaseType === 'text' || lowerCaseType === 'html' || lowerCaseType === 'richtext/html' ? lowerCaseType : 'unknown';
};
const getBrokenImageViewNode = (img) => {
    var _a;
    const wrapper = document.createElement('div');
    Array.from((_a = img === null || img === void 0 ? void 0 : img.attributes) !== null && _a !== void 0 ? _a : []).forEach(attr => {
        var _a;
        wrapper.setAttribute(attr.nodeName, (_a = attr.nodeValue) !== null && _a !== void 0 ? _a : '');
    });
    wrapper.setAttribute('class', 'broken-image-wrapper');
    wrapper.setAttribute('data-ui-id', 'broken-image-icon');
    return wrapper;
};
const isMessageValidToRender = (message) => {
    var _a, _b, _c, _d;
    if (message.deletedOn) {
        return false;
    }
    if (((_a = message.metadata) === null || _a === void 0 ? void 0 : _a.fileSharingMetadata) || ((_c = (_b = message.content) === null || _b === void 0 ? void 0 : _b.attachments) === null || _c === void 0 ? void 0 : _c.length)) {
        return true;
    }
    return !!(message.content && ((_d = message.content) === null || _d === void 0 ? void 0 : _d.message) !== '');
};
/**
 * Selector for {@link MessageThread} component.
 *
 * @public
 */
export const messageThreadSelector = messageThreadSelectorWithThread();
//# sourceMappingURL=messageThreadSelector.js.map