import find from 'lodash-es/find';

import { POST, ACTION } from 'mweb/chat/events/messageEvent';
import { BAN, TIMEOUT } from 'mweb/chat/events/moderationEvent';
import { DISCONNECTED, RECONNECT } from 'mweb/chat/events/statusEvent';
import { RESUBSCRIPTION } from 'mweb/chat/events/subscribeEvent';
import { ChatEvent } from 'mweb/chat/events/baseChatEvent';

const DEFAULT_MAX_BUFFER_SIZE = 250;

export interface ChatEventMessageHandler {
  (message: { data: ChatEvent }): void;
}

export default class ChatBuffer {
  private buffer: Array<ChatEvent>;
  private moderatedUsers: Set<string>;
  private dirty: boolean;

  constructor(private maxSize: number = DEFAULT_MAX_BUFFER_SIZE) {
    this.buffer = [];
    this.moderatedUsers = new Set();
    this.dirty = false;
  }

  consumeChatEvent = (message: { data: ChatEvent }): void => {
    const data = message.data;
    switch (data.type) {
      case BAN:
      case TIMEOUT:
        const { username, duration } = data;
        if (this.moderatedUsers.has(username)) {
          return;
        }
        this.buffer.forEach(event => {
          if (event.type === POST || event.type === ACTION) {
            if (username === event.user.username && !event.deleted) {
              event.deleted = true;
            }
          } else if (event.type === RESUBSCRIPTION && event.messageParts) {
            if (
              event.user &&
              event.user.username === username &&
              !event.deleted
            ) {
              event.deleted = true;
            }
          }
        });
        this.moderatedUsers.add(username);
        if (duration) {
          // this is for debouncing duplicate moderation messages during the timeout
          setTimeout(this.unmoderateUser(username), duration * 1000);
        }
        break;
      case DISCONNECTED:
        // if we want to show these, we'd need the same de-duping as reconnect so just delete the next line
        return;
      case RECONNECT:
        // tmi.js sends duplicate connection-related events frequently
        if (find(this.buffer, e => e.id === data.id)) {
          return;
        }
    }
    this.buffer.push(data);
    this.dirty = true;
  };

  toArray(): ReadonlyArray<ChatEvent> {
    this.buffer = this.buffer.slice(-this.maxSize);
    this.dirty = false;
    return this.buffer;
  }

  get isDirty(): boolean {
    return this.dirty;
  }

  // length() and moderatedUserCount() are only for testability
  get length(): number {
    return this.buffer.length;
  }

  get moderatedUserCount(): number {
    return this.moderatedUsers.size;
  }

  private unmoderateUser = (username: string): Function => {
    return () => {
      this.moderatedUsers.delete(username);
    };
  };
}
