import getSelector from '../../utilities/selector';
import { recordingControls } from './constants';
import { eventsToRecord, inputKeyCodes } from '../code-generator/constants';
import { findLastIndex, isEmpty, isEqual } from 'lodash';
import { overlaySelectors } from '../overlay-controller/constants';
import { HIGHTLIGHT_TYPES } from '../highlight/constants';

export default class Recorder {
  constructor({ store } = {}) {
    const { recording } = store.getState();
    this.store = store;
    this._eventLogs = new Map(recording?.map((e, i) => [i, e]) || []);
    this._previousEvent = null;
    this._isTopFrame = window.location === window.parent.location;
    this._isRecordingClicks = true;
    this.events = Object.values(eventsToRecord);
    window.addEventListener('message', this._handleMessage);
  }

  _handleMessage = (event) => {
    if (event?.data?.control === 'addHighlight' || event?.data?.control === 'removeHighlight') {
      const { isTableMode, hightLightingSelectors, recording } = this.store.getState();
      const eventLogArray = recording;
      isTableMode
        ? this._handleTableMode(eventLogArray, hightLightingSelectors)
        : this._handleNonTableMode(eventLogArray, hightLightingSelectors);
    }
  };

  _handleTableMode = (eventLogArray, hightLightingSelectors) => {
    const beforeunloadIndex = findLastIndex(eventLogArray, (event) => event.action === 'beforeunload') || 0;
    const extractDataIndex = findLastIndex(eventLogArray, (event) => event.action === 'extract-data');
    let extractTableSelector,
      paginationSelector,
      nextPageSelector = '';
    hightLightingSelectors.map((selector) => {
      if (isEqual(selector.type, HIGHTLIGHT_TYPES.EXTRACT_TABLE)) extractTableSelector = selector;
      if (isEqual(selector.type, HIGHTLIGHT_TYPES.EXTRACT_PAGINATION)) paginationSelector = selector;
      if (isEqual(selector.type, HIGHTLIGHT_TYPES.EXTRACT_NEXT_PAGE)) nextPageSelector = selector;
    });

    if (beforeunloadIndex < extractDataIndex) {
      isEmpty(extractTableSelector) && isEmpty(paginationSelector) && isEmpty(nextPageSelector)
        ? this._logEvent(extractDataIndex, null)
        : this._logEvent(extractDataIndex, {
            action: 'extract-data',
            selector: extractTableSelector?.selector,
            isTable: true,
            hasPagination: !!paginationSelector?.selector || true,
            paginatedStart: '1',
            paginatedEnd: '1',
            paginationSelector: paginationSelector?.selector,
            nextPageSelector: nextPageSelector?.selector,
          });
    } else {
      this._logEvent(null, {
        action: 'extract-data',
        selector: extractTableSelector?.selector,
        isTable: true,
        hasPagination: false,
        paginatedStart: '1',
        paginatedEnd: '1',
        paginationSelector: paginationSelector?.selector,
        nextPageSelector: nextPageSelector?.selector,
      });
    }
    this.store.updateSelectors('', '');
  };

  _handleNonTableMode = (eventLogArray, hightLightingSelectors) => {
    const beforeunloadIndex = findLastIndex(eventLogArray, (event) => event.action === 'beforeunload') || 0;
    const extractDataIndex = findLastIndex(eventLogArray, (event) => event.action === 'extract-data');
    if (beforeunloadIndex < extractDataIndex) {
      const extractDataEvent = this._eventLogs.get(extractDataIndex);
      if (hightLightingSelectors.length > 0) {
        extractDataEvent.selectedFields = hightLightingSelectors.map((selector, index) => {
          return {
            selector: selector.selector,
            name: selector?.name || `field ${index + 1}`,
            id: `field_${index + 1}`,
          };
        });
        this._logEvent(extractDataIndex, extractDataEvent);
      } else {
        this._logEvent(extractDataIndex, null);
      }
    } else {
      console.log({ hightLightingSelectors });
      this._logEvent(null, {
        action: 'extract-data',
        // selector: hightLightingSelectors[0]?.selector,
        isTable: false,
        hasPagination: false,
        paginatedStart: '1',
        paginatedEnd: '1',
        selectedFields: hightLightingSelectors.map((selector, index) => {
          return {
            selector: selector.selector,
            name: selector?.name || `field ${index + 1}`,
            id: `field_${index + 1}`,
          };
        }),
      });
    }
    this.store.updateSelectors(HIGHTLIGHT_TYPES.EXTRACT_FIELD, '');
  };

  init = (cb) => {
    this._addAllListeners();
    cb && cb();
    if (this._eventLogs.size == 0) {
      this._logEvent(null, { control: recordingControls.EVENT_RECORDER_STARTED });
      this._logEvent(null, {
        control: recordingControls.GET_CURRENT_URL,
        action: 'GOTO',
        href: window.location.href,
      });
      this._logEvent(null, {
        control: recordingControls.GET_VIEWPORT_SIZE,
        coordinates: { width: window.innerWidth, height: window.innerHeight },
      });
    }
  };

  _addAllListeners = () => {
    this.events.forEach((type) => {
      window.addEventListener(type, this._recordEvent);
    });
  };

  _removeAllListeners = () => {
    this.events.forEach((type) => window.removeEventListener(type, this._recordEvent, true));
  };

  _logEvent = (key, eventLog) => {
    // if key is empty, set key to the next available index
    key = key && key > 0 ? key : this._eventLogs.size;

    if (!isEmpty(eventLog)) {
      // add event to eventLogs
      if (eventLog?.action === 'click' && !this._isRecordingClicks) {
        return;
      }
      this._eventLogs.set(key, eventLog);
    } else {
      // remove event from eventLogs
      this._eventLogs.delete(key);
      this._eventLogs = new Map(Array.from(this._eventLogs.values()).map((e, i) => [i, e]));
    }
    this.store.setRecording(this._getEventLogs());
  };

  _getEventLogs = () => {
    return Array.from(this._eventLogs.values(), (event, index) => {
      return {
        ...event,
        id: index,
      };
    });
  };

  _clearEventLog = () => {
    this._eventLogs.clear();
  };

  disableClickRecording = () => {
    this._isRecordingClicks = false;
  };

  enableClickRecording = () => {
    this._isRecordingClicks = true;
  };

  static _getCoordinates(evt) {
    const eventsWithCoordinates = {
      mouseup: true,
      mousedown: true,
      mousemove: true,
      mouseover: true,
    };

    return eventsWithCoordinates[evt.type] ? { x: evt.clientX, y: evt.clientY } : null;
  }

  _recordEvent = (e) => {
    try {
      const selector = e.type !== 'beforeunload' && e.type !== 'load' ? getSelector(e) : '';

      if (this._isIgnoredSelector(selector)) return;

      const initialEvent = this._createInitialEvent(e, selector);

      if (this._isDuplicateEvent(e)) return;

      this._handleEvent(e, initialEvent, selector);

      this._previousEvent = e;
    } catch (err) {
      console.error(err);
    }
  };

  _isIgnoredSelector(selector) {
    return selector.includes('#' + 'custom-context-menu') || selector.includes('#' + overlaySelectors.OVERLAY_ID);
  }

  _createInitialEvent(e, selector) {
    return {
      selector,
      value: e.target.value || '',
      tagName: e.target.tagName || undefined,
      action: e.type,
      keyCode: e.keyCode ? e.keyCode : undefined,
      key: e.key ? e.key : undefined,
      href: e.target.href ? e.target.href : window?.location?.href || undefined,
      coordinates: Recorder._getCoordinates(e),
    };
  }

  _isDuplicateEvent(e) {
    return this._previousEvent && this._previousEvent.timeStamp === e.timeStamp;
  }

  _handleEvent(e, initialEvent) {
    switch (e.type) {
      case eventsToRecord.INPUT:
        if (
          this._eventLogs.has(this._eventLogs.size - 1) &&
          this._eventLogs.get(this._eventLogs.size - 1)?.action === eventsToRecord.INPUT
        ) {
          this._handleInputEvent(e);
        } else {
          this._logEvent(null, initialEvent);
        }
        break;
      case eventsToRecord.CLICK:
        this._handleClickEvent(e, initialEvent);
        break;
      case eventsToRecord.KEYDOWN:
        this._handleKeyDownEvent(e, initialEvent);
        break;
      default:
        this._logEvent(null, initialEvent);
    }
  }

  _handleInputEvent(e) {
    const lastInputLog = this._eventLogs.get(this._eventLogs.size - 1);
    console.log({ lastInputLog });
    this._logEvent(this._eventLogs.size - 1, {
      ...lastInputLog,
      value: e.target.value,
    });
  }

  _handleClickEvent(e, initialEvent) {
    this._logEvent(null, initialEvent);
  }

  _handleKeyDownEvent(e, initialEvent) {
    if (e.target.tagName !== 'INPUT' && !inputKeyCodes.includes(e.keyCode)) {
      this._logEvent(null, initialEvent);
    } else if (e.ctrlKey || e.metaKey) {
      if (e.key == 'a') {
        this._logEvent(null, {
          ...initialEvent,
          action: 'select-all',
        });
      } else if (e.key == 'c') {
        this._logEvent(null, {
          ...initialEvent,
          action: 'copy',
        });
      } else if (e.key == 'v') {
        this._logEvent(null, {
          ...initialEvent,
          action: 'paste',
        });
      }
    }
  }
}
