import startCase from 'lodash-es/startCase';
import flatMap from 'lodash-es/flatMap';

import { UserStatePayloadEmotes, UserStatePayload } from 'mweb/chat/chatClient';
import { Badge } from 'mweb/chat/badgerService';
import {
  getUsernameDisplay,
  UsernameDisplayData,
} from 'mweb/chat/events/utils/getUsernameDisplay';

const BitsRegExp = /\b(cheer|streamlabs|muxy|kappa|pogchamp|kreygasm|swiftrage)(\d+)\b/gi;

/**
 * Link parsing rules
 * - no immediately adjacent illegal chars on front or back, but they must be captured and separated by a space
 * - protocol is optional
 * - any number of subdomains
 * - tld must be between 2 and 6 characters
 */
const LinkRegExp = /([^\w@#%\-+=:~])?(?:(https?:\/\/)?(?:[\w@#%\-+=:~]+\.)+[a-z]{2,6}(?:\/[\w./@#%&()\-+=:?~]*)?)([^\w./@#%&()\-+=:?~]|\s|$)/g;

export interface EmoteData {
  images: {
    [scale: string]: string;
  };
  alt?: string;
  cheerAmount?: number;
  cheerColor?: string;
}

interface EmotePosition {
  startIndex: number;
  endIndex: number;
  data: EmoteData;
}

export const TEXT = 'TEXT';
interface TextMessagePart {
  type: typeof TEXT;
  content: string;
}

export interface LinkData {
  displayText: string;
  url: string;
}

export const LINK = 'LINK';
interface LinkMessagePart {
  type: typeof LINK;
  content: LinkData;
}

export const EMOTE = 'EMOTE';
interface EmoteMessagePart {
  type: typeof EMOTE;
  content: EmoteData;
}

export type MessagePart = TextMessagePart | LinkMessagePart | EmoteMessagePart;

export interface MessageEventUserData extends UsernameDisplayData {
  username: string;
  color: string | null;
}

export interface MessageData {
  badges: Badge[];
  user: MessageEventUserData;
  messageParts: MessagePart[] | undefined;
  deleted: boolean;
}

export function createMessageData(
  message: string,
  userstate: UserStatePayload,
  badges: Badge[],
): MessageData {
  return {
    badges,
    user: {
      ...getUsernameDisplay(userstate['display-name'], userstate.username),
      username: userstate.username,
      color: userstate.color,
    },
    messageParts: message
      ? messagePartBuilder(message, userstate.emotes, userstate.bits)
      : undefined,
    deleted: false,
  };
}

function getSymbolsInString(message: string): string[] {
  // From the article JavaScript Has a Unicode Issue https://mathiasbynens.be/notes/javascript-unicode#iterating-over-symbols
  const regexCodePoint = /[^\uD800-\uDFFF]|[\uD800-\uDBFF][\uDC00-\uDFFF]|[\uD800-\uDFFF]/g;
  return message.match(regexCodePoint) || [];
}

export function messagePartBuilder(
  message: string,
  emotes: UserStatePayloadEmotes | null,
  bits: number | undefined,
): MessagePart[] {
  let parts: MessagePart[] = [];
  let lastEndIndex = 0;
  const allEmotes: EmotePosition[] = Array(message.length);
  const characters = getSymbolsInString(message);
  const messageHasBits = !!bits;

  if (emotes) {
    Object.keys(emotes).forEach((emote: string) => {
      const ranges = emotes[emote];
      ranges.forEach((range: string) => {
        const [start, end] = range.split('-');
        const startIndex = parseInt(start);
        const endIndex = parseInt(end) + 1;
        allEmotes[startIndex] = {
          startIndex,
          endIndex,
          data: {
            images: {
              '1x': makeEmoteURL(emote, 1),
              '2x': makeEmoteURL(emote, 2),
              '4x': makeEmoteURL(emote, 4),
            },
          },
        };
      });
    });
  }

  allEmotes.forEach((emote: EmotePosition) => {
    const precedingText = characters
      .slice(lastEndIndex, emote.startIndex)
      .join('');
    if (precedingText) {
      if (messageHasBits) {
        const messageParts = flatMap(getCheerOutOfText(precedingText), part => {
          if (part.type === TEXT) {
            return getLinksOutOfText(part.content);
          }
          return part;
        });
        parts = parts.concat(messageParts);
      } else {
        parts = parts.concat(getLinksOutOfText(precedingText));
      }
    }
    parts.push({
      type: EMOTE,
      content: {
        alt: characters.slice(emote.startIndex, emote.endIndex).join(''),
        ...emote.data,
      },
    });
    lastEndIndex = emote.endIndex;
  });

  const trailingText = characters.slice(lastEndIndex).join('');
  if (trailingText) {
    if (messageHasBits) {
      const lastParts = flatMap(getCheerOutOfText(trailingText), part => {
        if (part.type === TEXT) {
          return getLinksOutOfText(part.content);
        }
        return part;
      });
      parts = parts.concat(lastParts);
    } else {
      parts = parts.concat(getLinksOutOfText(trailingText));
    }
  }

  return parts;
}

function getCheerOutOfText(message: string): MessagePart[] {
  const parts: MessagePart[] = [];
  let match = BitsRegExp.exec(message);
  let lastEndIndex = 0;
  while (match) {
    const startIndex = match.index;
    const emote = match[1].toLowerCase();
    const amount = parseInt(match[2]);
    const { floor, color } = getBitsRangeAndColor(amount);
    if (startIndex > lastEndIndex) {
      parts.push({
        type: TEXT,
        content: message.slice(lastEndIndex, startIndex),
      });
    }
    parts.push({
      type: EMOTE,
      content: {
        images: {
          '1x': makeCheermoteURL(emote, floor, 1),
          '1.5x': makeCheermoteURL(emote, floor, 1.5),
          '2x': makeCheermoteURL(emote, floor, 2),
          '3x': makeCheermoteURL(emote, floor, 3),
          '4x': makeCheermoteURL(emote, floor, 4),
        },
        alt: startCase(emote),
        cheerAmount: amount,
        cheerColor: color,
      },
    });
    lastEndIndex = BitsRegExp.lastIndex;
    match = BitsRegExp.exec(message);
  }
  if (lastEndIndex < message.length) {
    parts.push({
      type: TEXT,
      content: message.slice(lastEndIndex),
    });
  }
  return parts;
}

function getLinksOutOfText(text: string): MessagePart[] {
  const parts: MessagePart[] = [];
  let lastEndIndex = 0;
  let needsTrailingSpace = false;
  let match = LinkRegExp.exec(text);
  while (match) {
    let startIndex = match.index;
    let needsPrecedingSpace = false;
    const leadingChar = match[1];
    if (leadingChar) {
      startIndex += 1;
      if (!/\s/.test(leadingChar)) {
        needsPrecedingSpace = true;
      }
    }

    if (startIndex !== lastEndIndex) {
      parts.push({
        type: TEXT,
        content: `${needsTrailingSpace ? ' ' : ''}${text.slice(
          lastEndIndex,
          startIndex,
        )}${needsPrecedingSpace ? ' ' : ''}`,
      });
    }
    needsTrailingSpace = false;

    let endIndex = LinkRegExp.lastIndex;
    const trailingChar = match[3];
    if (trailingChar) {
      endIndex -= 1;
      if (!/\s/.test(trailingChar)) {
        needsTrailingSpace = true;
      }
    }

    const protocol = match[2];
    parts.push({
      type: LINK,
      content: {
        displayText: text.slice(startIndex, endIndex),
        url: `${protocol ? '' : 'https://'}${text.slice(startIndex, endIndex)}`,
      },
    });
    lastEndIndex = endIndex;
    match = LinkRegExp.exec(text);
  }

  const trailingText = text.slice(lastEndIndex);
  if (trailingText) {
    parts.push({
      type: TEXT,
      content: `${needsTrailingSpace ? ' ' : ''}${trailingText}`,
    });
  }

  return parts;
}

export const BITS_COLOR_MAP = {
  red: '#f43021',
  blue: '#0099fe',
  green: '#1db2a5',
  purple: '#9c3ee8',
  gray: '#979797',
};

function getBitsRangeAndColor(
  amount: number,
): { floor: number; color: string } {
  let floor: number;
  let color: string;

  if (amount >= 10000) {
    floor = 10000;
    color = BITS_COLOR_MAP.red;
  } else if (amount >= 5000) {
    floor = 5000;
    color = BITS_COLOR_MAP.blue;
  } else if (amount >= 1000) {
    floor = 1000;
    color = BITS_COLOR_MAP.green;
  } else if (amount >= 100) {
    floor = 100;
    color = BITS_COLOR_MAP.purple;
  } else {
    floor = 1;
    color = BITS_COLOR_MAP.gray;
  }

  return { floor, color };
}

export function makeCheermoteURL(
  emote: string,
  clamp: number,
  size: number,
): string {
  return `https://d3aqoihi2n8ty8.cloudfront.net/actions/${emote}/light/animated/${clamp}/${size}.gif`;
}

function makeEmoteURL(emote: string, size: number): string {
  if (size === 4) {
    size = 3;
  }
  return `https://static-cdn.jtvnw.net/emoticons/v1/${emote}/${size}.0`;
}
