#include channels.js
#include common.js
#include parsing.js
#include title.js
#include txtrigs.js
#include url.js
#include users.js
#include utility.js
#include weeb.js
#include sound/umisound.js
#include ui/emotes.js

Umi.UI.Messages = (function() {
    let forceUserInfo = false;
    let lastMsgUser = null;
    let lastMsgChannel = null;
    let lastWasTiny = null;

    const title = new MamiWindowTitle({
        getName: () => futami.get('title'),
    });

    window.addEventListener('focus', () => title.clear());

    const botMsgs = {
        'say': { text: '%0' },
        'generr': { text: 'Something unexpected happened.' },
        'flwarn': { text: 'You are about to hit the flood limit! If you continue you will be kicked.' },
        'unban': { text: '%0 is no longer banned.', sound: 'unban' },
        'delerr': { text: 'You are not allowed to delete this message.' },
        'notban': { text: '%0 is not banned.' },
        'whoerr': { text: '%0 does not exist.' },
        'join': { text: '%0 has joined.', action: 'has joined', sound: 'join' },
        'leave': { text: '%0 has disconnected.', action: 'has disconnected', avatar: 'greyscale', sound: 'leave' },
        'jchan': { text: '%0 has joined the channel.', action: 'has joined the channel', sound: 'join' },
        'lchan': { text: '%0 has left the channel.', action: 'has left the channel', avatar: 'greyscale', sound: 'leave' },
        'kick': { text: '%0 got bludgeoned to death.', action: 'got bludgeoned to death', avatar: 'invert', sound: 'kick' },
        'flood': { text: '%0 got kicked for flood protection.', action: 'got kicked for flood protection', avatar: 'invert', sound: 'flood' },
        'timeout': { text: '%0 exploded.', action: 'exploded', avatar: 'greyscale', sound: 'timeout' },
        'nick': { text: '%0 changed their name to %1.', action: 'changed their name to %1' },
        'crchan': { text: 'Channel %0 has been created.' },
        'delchan': { text: 'Channel %0 has been deleted.' },
        'cpwdchan': { text: 'Channel password has been changed.' },
        'cprivchan': { text: 'Channel access level has been changed.' },
        'ipaddr': { text: 'IP address of %0 is %1.' },
        'cmdna': { text: 'You are not allowed to use %0.' },
        'nocmd': { text: 'Command %0 does not exist.' },
        'cmderr': { text: 'You did not use that command correctly.' },
        'usernf': { text: '%0 is not logged in right now!' },
        'kickna': { text: 'You are not allowed to kick %0.' },
        'samechan': { text: 'You are already in channel %0.' },
        'ipchan': { text: 'You are not allowed to join channel %0.' },
        'nochan': { text: 'Channel %0 does not exist.' },
        'nopwchan': { text: 'Channel %0 requires a password. Use /join %0 <password>' },
        'ipwchan': { text: 'Wrong password for channel %0.' },
        'inchan': { text: 'Channel name contains invalid characters.' },
        'nischan': { text: 'A channel with the name %0 already exists.' },
        'ndchan': { text: 'You are not allowed to deleted channel %0.' },
        'namchan': { text: 'You are not allowed to edit channel %0.' },
        'nameinuse': { text: 'Someone else is already using the name %0.' },
        'rankerr': { text: 'You cannot set the access level of a channel higher than that of your own.' },
        'banlist': {
            text: 'Banned: %0',
            filter: args => {
                const bans = args[0].split(', ');
                for(const i in bans)
                    bans[i] = bans[i].slice(92, -4);

                args[0] = bans.join(', ');
                return args;
            },
        },
        'who': {
            text: 'Online: %0',
            filter: args => {
                const users = args[0].split(', ');
                for(const i in users) {
                    const isSelf = users[i].includes(' style="font-weight: bold;"');
                    users[i] = users[i].slice(isSelf ? 102 : 75, -4);
                    if(isSelf) users[i] += ' (You)';
                }

                args[0] = users.join(', ');
                return args;
            },
        },
        'whochan': {
            text: 'Online in %0: %1',
            filter: args => {
                const users = args[1].split(', ');
                for(const i in users) {
                    const isSelf = users[i].includes(' style="font-weight: bold;"');
                    users[i] = users[i].slice(isSelf ? 102 : 75, -4);
                    if(isSelf) users[i] += ' (You)';
                }

                args[1] = users.join(', ');
                return args;
            },
        },
    };

    const formatTemplate = (template, args) => {
        if(typeof template !== 'string')
            template = '';

        if(Array.isArray(args))
            for(let i = 0; i < args.length; ++i) {
                const arg = args[i] === undefined || args[i] === null ? '' : args[i].toString();
                template = template.replace(new RegExp(`%${i}`, 'g'), arg);
            }

        return template;
    };

    return {
        Add: function(msg) {
            const currentChannel = Umi.Channels.Current();
            const channelName = msg.getChannel();
            const sender = msg.getUserV2();
            const isBot = sender.id === '-1';
            const isOutgoing = Umi.User.isCurrentUser(sender);
            const hasSeen = msg.hasSeen();
            const displayMessage = currentChannel === null || channelName === null || channelName === currentChannel.name;
            const notifyPM = !displayMessage && !isOutgoing && !hasSeen && channelName.startsWith('@');

            let isTiny = false,
                skipTextParsing = false,
                msgText = msg.getText(),
                msgTextLong = msgText;

            let eBase, eAvatar, eText, eMeta, eUser;

            let avatarUser = sender,
                avatarSize = '80';

            let soundName = isOutgoing ? 'outgoing' : 'incoming',
                soundVolume, soundRate, soundIsLegacy = true;

            const userClass = `message--user-${sender.id}`;

            const classes = ['message', userClass];
            const styles = {};

            const avatarClasses = ['message__avatar'];

            const msgIsFirst = forceUserInfo || lastMsgUser !== sender.id || lastMsgChannel !== msg.getChannel();
            if(msgIsFirst) {
                forceUserInfo = false;
                classes.push('message--first');
            }

            if(msg.isAction()) {
                isTiny = true;
                classes.push('message-action');
            }

            if(sender.id === "136")
                styles.transform = 'scaleY(' + (0.76 + (0.01 * Math.max(0, Math.ceil(Date.now() / (7 * 24 * 60 * 60000)) - 2813))).toString() + ')';

            const msgDateTimeObj = msg.getTime();
            const msgDateTime = msgDateTimeObj.getHours().toString().padStart(2, '0')
                + ':' + msgDateTimeObj.getMinutes().toString().padStart(2, '0')
                + ':' + msgDateTimeObj.getSeconds().toString().padStart(2, '0');

            if(isBot) {
                const botInfo = msg.getBotInfo();
                soundName = botInfo.isError ? 'error' : 'server';

                if(botMsgs.hasOwnProperty(botInfo.type)) {
                    const bmInfo = botMsgs[botInfo.type];

                    let bArgs = botInfo.args;
                    if(typeof bmInfo.filter === 'function')
                        bArgs = bmInfo.filter(bArgs);

                    if(typeof bmInfo.sound === 'string')
                        soundName = bmInfo.sound;

                    let actionSuccess = false;
                    if(typeof bmInfo.action === 'string' && mami.settings.get('fancyInfo')) {
                        const target = botInfo.target ?? Umi.Users.FindExact(bArgs[0]) ?? Umi.Users.FindExact('~' + bArgs[0]); // shitty fix for server sending invalid data

                        if(target) {
                            actionSuccess = true;
                            isTiny = true;
                            skipTextParsing = true;
                            avatarUser = target;

                            $ari(classes, userClass);

                            msgText = formatTemplate(bmInfo.action, bArgs);
                            if(typeof bmInfo.avatar === 'string')
                                avatarClasses.push(`avatar-filter-${bmInfo.avatar}`);
                        }
                    }

                    msgTextLong = formatTemplate(bmInfo.text, bArgs);

                    if(!actionSuccess)
                        msgText = msgTextLong;
                } else
                    msgText = msgTextLong = `!!! Received unsupported message type: ${botInfo.type} !!!`;
            } else {
                if(mami.settings.get('playJokeSounds'))
                    try {
                        const trigger = mami.textTriggers.getTrigger(msgText);
                        if(trigger.isSoundType()) {
                            soundIsLegacy = false;
                            soundName = trigger.getRandomSoundName();
                            soundVolume = trigger.getVolume();
                            soundRate = trigger.getRate();
                        }
                    } catch(ex) {}
            }

            let avatarUrl = futami.get('avatar');
            if(typeof avatarUrl !== 'string' || avatarUrl.length < 1)
                avatarUrl = undefined;
            else
                avatarUrl = avatarUrl.replace('{user:id}', avatarUser.id)
                    .replace('{resolution}', avatarSize)
                    .replace('{user:avatar_change}', avatarUser.avatarChangeTime);

            if(displayMessage) {
                if(isTiny) {
                    if(!msgIsFirst) // small messages must always be "first"
                        classes.push('message--first');
                    classes.push('message-tiny');

                    avatarSize = '40';

                    if(msgText.indexOf("'") !== 0 || (msgText.match(/\'/g).length % 2) === 0)
                        msgText = "\xA0" + msgText;

                    eBase = <div id={`message-${msg.getId()}`} class={classes} style={styles}>
                        {eAvatar = <div class={avatarClasses}/>}
                        <div class="message__container">
                            {eMeta = <div class="message__meta">
                                {eUser = <div class="message__user" style={{ color: avatarUser.colour }}>{avatarUser.name}</div>}
                                {eText = <div class="message-tiny-text"/>}
                                <div class="message__time">{msgDateTime}</div>
                            </div>}
                        </div>
                    </div>;
                } else {
                    eBase = <div id={`message-${msg.getId()}`} class={classes} style={styles}>
                        {eAvatar = <div class={avatarClasses}/>}
                        <div class="message__container">
                            {eMeta = <div class="message__meta">
                                {eUser = <div class="message__user" style={{ color: avatarUser.colour }}>{avatarUser.name}</div>}
                                <div class="message__time">{msgDateTime}</div>
                            </div>}
                            {eText = <div class="message__text"/>}
                        </div>
                    </div>;
                }

                eText.innerText = msgText;

                if(!skipTextParsing) {
                    eText = Umi.UI.Emoticons.Parse(eText, msg);
                    eText = Umi.Parsing.Parse(eText, msg);

                    const urls = [];

                    if(mami.settings.get('autoParseUrls')) {
                        const textSplit = eText.innerText.split(' ');
                        for(const textPart of textSplit) {
                            const uri = Umi.URI.Parse(textPart);

                            if(uri !== null && uri.Slashes !== null) {
                                urls.push(textPart);

                                const anchorElem = <a class="markup__link" href={textPart} target="_blank" rel="nofollow noreferrer noopener">{textPart}</a>;
                                eText.innerHTML = eText.innerHTML.replace(textPart.replace(/&/g, '&amp;'), anchorElem.outerHTML);
                            }
                        }
                    }

                    if(mami.settings.get('weeaboo')) {
                        eText.appendChild($t(Weeaboo.getTextSuffix(sender)));

                        const kaomoji = Weeaboo.getRandomKaomoji(true, msg);
                        if(kaomoji) {
                            eText.appendChild($t(' '));
                            eText.appendChild($t(kaomoji));
                        }
                    }

                    if(mami.settings.get('weeaboo'))
                        eUser.appendChild($t(Weeaboo.getNameSuffix(sender)));
                }

                if(isTiny !== lastWasTiny) {
                    if(!msgIsFirst)
                        eBase.classList.add('message--first');
                    eBase.classList.add(isTiny ? 'message-tiny-fix' : 'message-big-fix');
                }
                lastWasTiny = isTiny;

                if(avatarUrl === undefined)
                    eAvatar.classList.add('message__avatar--disabled');
                else
                    eAvatar.style.backgroundImage = `url(${avatarUrl})`;

                const msgsList = $i('umi-messages');

                msgsList.appendChild(eBase);
                lastMsgUser = sender.id;
                lastMsgChannel = msg.getChannel();

                if(mami.settings.get('autoEmbedV1')) {
                    const callEmbedOn = eBase.querySelectorAll('a[onclick^="Umi.Parser.SockChatBBcode.Embed"]');
                    for(const embedElem of callEmbedOn)
                        if(embedElem.dataset.embed !== '1')
                            embedElem.click();
                }

                if(mami.settings.get('autoScroll'))
                    msgsList.scrollTop = msgsList.scrollHeight;
            }

            let isMentioned = false;
            const mentionTriggers = (mami.settings.get('notificationTriggers') || '').toLowerCase().split(' ');
            const currentUser = Umi.User.getCurrentUser();
            if(typeof currentUser === 'object' && typeof currentUser.name === 'string')
                mentionTriggers.push(currentUser.name.toLowerCase());

            const mentionText = ` ${msgTextLong} `.toLowerCase();
            for(const trigger of mentionTriggers) {
                if(trigger.trim() === '')
                    continue;

                if(mentionText.includes(` ${trigger} `)) {
                    isMentioned = true;
                    break;
                }
            }

            if(!isMentioned && mami.settings.get('onlySoundOnMention'))
                soundName = undefined;

            if(document.hidden) {
                if(mami.settings.get('flashTitle')) {
                    let titleText = isBot && mami.settings.get('showServerMsgInTitle')
                        ? ` ${msgTextLong}`
                        : ` ${sender.name}`;

                    // oops this won't work lol, we're filtering at the top
                    if(currentChannel !== null && currentChannel.name !== channelName)
                        titleText += ` @ ${channelName}`;

                    title.strobe([
                        `[ @] ${titleText}`,
                        `[@ ] ${titleText}`,
                    ]);
                }

                if(!hasSeen) {
                    Umi.UI.Channels.Unread(channelName);

                    if(mami.settings.get('enableNotifications') && isMentioned) {
                        const options = {};

                        options.body = 'Click here to see what they said.';
                        if(mami.settings.get('notificationShowMessage'))
                            options.body += "\n" + msgTextLong;

                        if(avatarUrl !== undefined)
                            options.icon = avatarUrl;

                        const notif = new Notification(`${sender.name} mentioned you!`, options);
                        notif.addEventListener('click', () => {
                            window.focus();
                        });
                        document.addEventListener('visibilitychange', () => {
                            if(document.visibilityState === 'visible')
                                notif.close();
                        });
                    }
                }
            }

            if(soundName !== undefined && !hasSeen) {
                if(soundIsLegacy)
                    soundName = Umi.Sound.Convert(soundName);

                mami.sound.library.play(soundName, soundVolume, soundRate);
            }

            if(eBase instanceof HTMLElement)
                mami.globalEvents.dispatch('umi:ui:message_add', {
                    element: eBase,
                    message: msg,
                });

            msg.markSeen();
        },
        Remove: function(msg) {
            forceUserInfo = true;
            lastMsgUser = null;
            lastMsgChannel = null;
            lastWasTiny = null;
            $ri(`message-${msg.getId()}`);
        },
        RemoveAll: function() {
            forceUserInfo = true;
            lastMsgUser = null;
            lastMsgChannel = null;
            lastWasTiny = null;
            $i('umi-messages').innerHTML = '';
        },
    };
})();
