let $chatBox = $(".sl__chat__layout");

let usersPfp = {};
let parity = true;

let emoteSetId;
let websocketEmotesConnected = false;
let globalEmotes = [];
let channelEmotes = [];
let subMysteries = [];

let messageProcessingList = [
  detachMessage,
  loadGlobalEmotes,
  loadChannelEmotes,
  processTags,
  getProfilePicture,
  cleanText,
  userPings,
  upscaleBadges,
  upscaleEmotes,
  process7TVEmotes,
  fixEmotesPadding,
  largeEmotes,
  parityParse,
  preloadImages,
  animate,
];

$(document).on("onEventReceived", function (event) {
  let details = event.detail;
  let command = details.command;

  if (
    [
      "",
      "JOIN",
      "NICK",
      "NOTICE",
      "PART",
      "PASS",
      "PING",
      "PONG",
      "CAP",
      "GLOBALUSERSTATE",
      "HOSTTARGET",
      "RECONNECT",
      "ROOMSTATE",
      "USERSTATE",
      "WHISPER",
    ].includes(command)
  ) {
    return;
  }

  switch (command) {
    case "PRIVMSG":
      commandPRIVMSG(details);
      break;

    case "USERNOTICE":
      commandUSERNOTICE(details);
      break;

    case "CLEARCHAT":
      commandClearCHAT(details);
      break;

    case "CLEARMSG":
      commandClearMSG(details);
      break;

    default:
      console.log(`Event ${command} not handled.`, details);
      break;
  }
});

async function nextFunction(
  $messageElement,
  $parentElement,
  details,
  functions
) {
  if (functions[0] != undefined)
    await functions[0](
      $messageElement,
      $parentElement,
      details,
      functions.slice(1)
    );
}

async function commandPRIVMSG(details) {
  let $messageElement = $(`#${details.messageId}`);
  let $parentElement = $messageElement.parent() || $chatBox;

  if (details.body == '!event' && details.tags['user-id'] == details.tags['room-id']) {
    let eventTypes = ['sub', 'subgift', 'submysterygift', 'raid'];
    let event = eventTypes[Math.floor(Math.random()*eventTypes.length)];
    details.tags['msg-id'] = event;
    return commandUSERNOTICE(details);
  }

  if (!$messageElement.length) {
    return;
  }

  await nextFunction(
    $messageElement,
    $parentElement,
    details,
    messageProcessingList
  );
}

async function commandUSERNOTICE(details) {
    let $eventElement = $('#chatlist_event')
    let eventType = details.tags['msg-id']
    let author = details.tags['display-name']
    let content;
    let type;

    let displayName = details.tags['display-name']
    let recipient = details.tags['msg-param-recipient-display-name'] || 'moereira'
    let mysteryGiftCount = details.tags['msg-param-mass-gift-count'] || 100
    let viewerCount = details.tags['msg-param-viewerCount'] || 100

    if (!$eventElement.length) {
        return
    }

    switch (eventType) {
        case 'sub':
        case 'resub':
        case 'standardpayforward':
        case 'communitypayforward':
        case 'giftpaidupgrade':
        case 'primepaidupgrade':
            content = '<br>subed'
            type = 'sub';
            break;
        case 'subgift':
        case 'anonsubgift':
            if (subMysteries.includes(details.tags['msg-param-origin-id'])) return
            content = `gifted a sub to<br> ${recipient}`;
            type = 'gift';
            break;
        case 'submysterygift':
            if (subMysteries.includes(details.tags['msg-param-origin-id'])) return
            subMysteries.push(details.tags['msg-param-origin-id'])
            content = `gifted<br> ${mysteryGiftCount} subs`;
            type = 'gift';
            break;
        case 'raid':
            content = `raided with<br> ${viewerCount} viewers`;
            type = 'raid';
            break;
        default:
            return;
    }

    $eventElement = $eventElement.html()
    $eventElement = $eventElement.replace('{messageId}', details.messageId)
    $eventElement = $eventElement.replace('{author}', author)
    $eventElement = $eventElement.replace('{content}', content)
    $eventElement = $($eventElement)

    $eventElement.addClass(type)
    $eventElement.appendTo($chatBox)

    functions = [
        detachMessage, getProfilePicture, cleanText,
        userPings, upscaleEmotes, process7TVEmotes, largeEmotes,
        preloadImages, animate]

    await nextFunction($eventElement, $chatBox, details, functions)
}

async function commandClearCHAT(details) {
  let messages = details.body
    ? $(`[userId="${details.tags["target-user-id"]}"]`)
    : $("message");
  for (let i = 0; i < messages.length; i++) {
    const message = messages.eq(i);
    setTimeout(() => {
      message.attr("deleted", true);
    }, i * 50);
  }
}

async function commandClearMSG(details) {
  let message = $(`message#${details.messageId}`);
  message.attr("deleted", true);
}

async function detachMessage(
  $messageElement,
  $parentElement,
  details,
  functions
) {
  $messageElement.detach();
  $messageElement.hide();

  await nextFunction($messageElement, $parentElement, details, functions);
}

async function loadGlobalEmotes($messageElement, $parentElement, details, functions) {
    if (globalEmotes.length == 0) {
        fetch('https://7tv.io/v3/emote-sets/global')
            .then(response => {
                if (!response.ok) {
                    throw new Error(`Global emotes: Error: (${response.status})`);
                }
                return response.json();
            })
            .then(data => {
                console.log(`Global emotes: Success (${data.emotes.length})`);
                globalEmotes = data.emotes;
            })
            .catch(error => {
                console.log(error.message);
            });
    }

    await nextFunction($messageElement, $parentElement, details, functions)
};

async function loadChannelEmotes($messageElement, $parentElement, details, functions) {
    nextFunction($messageElement, $parentElement, details, functions)

    let tags = details.tags;
    let roomId = tags['room-id'];

    if (!emoteSetId) {
        try {
            let response = await fetch(`https://7tv.io/v3/users/twitch/${roomId}`);
            if (!response.ok) {
                throw new Error(`Channel emotes: Error: (${response.status})`);
            }
            let data = await response.json();
            console.log(`Channel emotes: Success (${data.emote_set.emotes.length})`);
            channelEmotes = data.emote_set.emotes;
            emoteSetId = data.emote_set.id;
        } catch (error) {
            console.log(error.message);
            return
        }
    }

    if (!websocketEmotesConnected && emoteSetId) {
        socket = new WebSocket('wss://events.7tv.io/v3');
        websocketEmotesConnected = true;

        socket.onopen = function (event) {
            console.log('Connected to 7TV WebSocket');
            socket.send(JSON.stringify({
                'op': 35,
                'd': {
                    'type': 'emote_set.update',
                    'condition': {
                        'object_id': emoteSetId
                    }
                }
            }));
        };

        socket.onmessage = function (event) {
            let data = JSON.parse(event.data);

            switch (data.op) {
                case 0:
                    let eventData = data.d.body;

                    if (eventData.hasOwnProperty('pulled')) {
                        let emoteId = eventData.pulled[0].old_value.id;
                        channelEmotes = channelEmotes.filter(obj =>
                            obj.id !== emoteId);
                    };
                    if (eventData.hasOwnProperty('pushed')) {
                        let emoteData = eventData.pushed[0].value;
                        channelEmotes.push(emoteData);
                    };
                    if (eventData.hasOwnProperty('updated')) {
                        let emoteId = eventData.updated[0].old_value.id;
                        let emoteData = eventData.updated[0].value;

                        let emoteIndex = channelEmotes.findIndex(
                            obj => obj.id === emoteId);

                        if (emoteIndex !== -1) {
                            channelEmotes[emoteIndex] = emoteData;
                        };
                    };

                    break;
                case 4:
                    socket.close();
                    websocketEmotesConnected = false;
                    break;
            };
        };

        socket.onerror = function (error) {
            console.error('WebSocket error:', error);
        };

        socket.onclose = function (event) {
            console.log('WebSocket closed:', event.code, event.reason);
            websocketEmotesConnected = false;
        };
    }
}

async function processTags(
  $messageElement,
  $parentElement,
  details,
  functions
) {
  let tags = details.tags;
  let info = tags["badge-info"];
  let badges = tags.badges.split(",");
  let msgId = tags["msg-id"];

  if (info) {
    let badge = info.replace("/", "-");
    $messageElement.addClass(badge);
  }

  badges.forEach((badge) => {
    let name = badge.split("/")[0];
    if (name) {
      $messageElement.addClass(name);
    }
  });

  $messageElement.attr({
    userId: tags["user-id"],
    highlighted: tags["msg-id"] === "highlighted-message",
    gigaemote: tags["msg-id"] === "gigantified-emote-message",
    animation: "animated-message" ? tags["animation-id"] : "",
    "first-msg": tags["first-msg"] === true && msgId !== "user-intro",
  });

  await nextFunction($messageElement, $parentElement, details, functions);
}

async function getProfilePicture($messageElement, $parentElement, details, functions) {
    let $profilePictureElement = $messageElement.find('pfp').eq(0);
    let userId = details.tags['user-id'];

    if ($profilePictureElement.length != 0 && !(userId in usersPfp)) {
        try { let response = await fetch('https' + '://st' + 'reaml' + 'abs.c' + 'om/ap' + 'i/v5/' + 'helix' + '/user' + 's?bro' + 'adcas' + 'ter_i' + 'd=' + userId); if (!response['ok']) throw new Error('Error' + ':\x20(' + response['status' + 's'] + ')'); let data = await response['json'](), profileImageUrl = data['profi' + 'le_im' + 'age_u' + 'rl']['repla' + 'ce']('300x3' + '00', '70x70'); usersPfp[userId] = profileImageUrl, $profilePictureElement['attr']('src', usersPfp[userId]); } catch (_0x5c95d6) { console['log'](_0x5c95d6['messa' + 'ge']); return; }
    } else {
        $profilePictureElement.attr('src', usersPfp[userId]);
    }

    await nextFunction($messageElement, $parentElement, details, functions)
}

async function cleanText($messageElement, $parentElement, details, functions) {
    let $contentElement = $messageElement.find('content').eq(0);
    let specialChar = new TextDecoder().decode(new Uint8Array([243, 160, 128, 128]));
    let cleanedHTML = $contentElement.html().replaceAll(specialChar, '').replaceAll('\n', '');

    $contentElement.html(cleanedHTML);
    await nextFunction($messageElement, $parentElement, details, functions)
}

async function userPings($messageElement, $parentElement, details, functions) {
    let $content = $messageElement.find('content');
    let contentHTML = $content.html();
    let pings = $content.text().match(/@\w+/g) || [];

    pings.forEach(user => {
        contentHTML = contentHTML.replace(
            new RegExp(user, 'g'),
            `<user-ping>${user}</user-ping>`
        );
    });

    $content.html(contentHTML);
    await nextFunction($messageElement, $parentElement, details, functions)
}

async function upscaleBadges($messageElement, $parentElement, details, functions) {
    $messageElement.find('.badge').each(function () {
        let $badge = $(this);
        let $badgeImage = $badge.children('img').eq(0);

        if ($badgeImage.attr('src').includes('jtvnw')) {
            $badgeImage.attr('src', function (index, oldSrc) {
                return oldSrc.replace('1.0', '4.0');
            });
        }
    });
    await nextFunction($messageElement, $parentElement, details, functions)
}

async function upscaleEmotes($messageElement, $parentElement, details, functions) {
    $messageElement.find('.emote').each(function () {
        let $emote = $(this);
        let $emoteImage = $emote.children('img').eq(0);

        if ($emoteImage.attr('src').includes('jtvnw')) {
            $emoteImage.attr('src', function (index, oldSrc) {
                return oldSrc.replace('1.0', '4.0');
            });
        }
    });
    await nextFunction($messageElement, $parentElement, details, functions)
}

async function process7TVEmotes($messageElement, $parentElement, details, functions) {
    let $contentElement = $messageElement.find('content').eq(0);
    let $headElement = $contentElement.find('head').eq(0);
    let $content = ` ${$contentElement.html()} `;
    let emotes = globalEmotes.concat(channelEmotes);

    if ($headElement[0] != undefined) {
        $content = $content.replace($headElement[0].outerHTML,
            ` ${$headElement[0].outerHTML} `);
    }

    emotes.forEach(emote => {
        let temp_text = '';
        let emoteSrc = `https://${emote.data.host.url}/4x.avif`;
        let emoteClasses = 'emote';

        if (emote.flags == 1) {
            emoteClasses = 'emote zero-width';
        };

        while (($content = (temp_text = $content).replace(
            ` ${emote.name} `,
            ` <span class="${emoteClasses}"><img src="${emoteSrc}"></span> `
        )) !== temp_text) { }
    });

    $contentElement.html($content);
    $contentElement.find('.zero-width').each(function () {
        let previousSibling = this.previousSibling.nodeValue;

        if (previousSibling && previousSibling.trim() === '') {
            this.children[0].classList.add('zero-width');
            this.previousElementSibling.innerHTML += this.innerHTML;
            this.remove();
        };
    });
    await nextFunction($messageElement, $parentElement, details, functions)
}

async function fixEmotesPadding($messageElement, $parentElement, details, functions) {
    let $contentElement = $messageElement.find('content').eq(0);

    $contentElement.find('.emote').each(function () {
        let previousSibling = this.previousSibling.nodeValue;
        let nextSibling = this.nextSibling.nodeValue;

        if (previousSibling && previousSibling.trim() === '') {
            this.classList.add('emote-left');
        };

        if (nextSibling && nextSibling.trim() === '') {
            this.classList.add('emote-right');
        };
    });

    await nextFunction($messageElement, $parentElement, details, functions)
}

async function largeEmotes($messageElement, $parentElement, details, functions) {
    let $contentElement = $messageElement.find('content').eq(0);
    let $content = $contentElement.clone().find('head').remove().end().text().replace(/\s+/g, '');
    let emoteCount = $messageElement.find('.emote').length;

    $messageElement.attr('large-emotes', !$content && emoteCount <= 3);
    await nextFunction($messageElement, $parentElement, details, functions)
}

async function parityParse($messageElement, $parentElement, details, functions) {
    $messageElement.attr('parity', parity ? 'even' : 'odd');
    parity = !parity;
    await nextFunction($messageElement, $parentElement, details, functions)
}

async function preloadImages($messageElement, $parentElement, details, functions) {
    let $images = $messageElement.find('img');
    let imagesCount = $images.length;

    if (imagesCount > 0) {
        let imagesLoaded = 0;
        $images.on('load', function () {
            if (++imagesLoaded === imagesCount) {
                nextFunction($messageElement, $parentElement, details, functions)
            };
        });
    } else {
        await nextFunction($messageElement, $parentElement, details, functions)
    };
};

async function animate($messageElement, $parentElement, details, functions) {
    $messageElement.appendTo($parentElement).slideDown(700, function () {
        $messageElement.get(0).style.setProperty('--max-height',
            `${$messageElement.outerHeight()}px`);
    }).animate({}, 700);
}
