const PreviewModel = (() => {
  // This is a blank GIF of the smallest possible size:
  const EMPTY_DATA = 'R0lGODlhAQABAAD/ACwAAAAAAQABAAACADs=';

  const getDefaults = () => ({
    id: '',
    lastPath: '',
    isUpdating: false,
    skipUpdate: false,
    imageData: EMPTY_DATA,
    initialData: EMPTY_DATA,
  });

  const emitter = mitt();
  const image = new Image();
  const state = getDefaults();
  const defaults = getDefaults();
  // State object for settings synchronization:
  const sync = { settings: null, isDirty: false };
  const settings = { canCreate: true, canUpdate: true };
  const cropSettings = { maxWidth: 400, maxHeight: 60, cropThreshold: 5 };

  const isValidPreview = (preview) => {
    return (
      preview != null &&
      typeof preview === 'object' &&
      typeof preview.id === 'string' &&
      typeof preview.width === 'number' &&
      typeof preview.height === 'number'
    );
  };

  const applySettings = (newSettings = {}) => {
    Object.assign(settings, _.pick(newSettings, ['canCreate', 'canUpdate']));
  };

  return {
    get isDisabled() {
      return _GobalModel.get('isedit') ? !settings.canUpdate : !settings.canCreate;
    },

    updateSettings(newSettings = {}) {
      const state = _GobalModel.get('state');

      if (state === 'main') {
        applySettings(newSettings);
      } else {
        sync.settings = newSettings;
        if (sync.isDirty) return;

        sync.isDirty = true;
        _MainView.once('before:render', () => {
          sync.isDirty = false;

          if (sync.settings) {
            applySettings(sync.settings);
            sync.settings = null;
          }
        });
      }
    },

    reset() {
      Object.assign(state, defaults);
    },

    state() {
      return Object.freeze({ ...state });
    },

    edit(preview) {
      if (!isValidPreview(preview)) return;

      Object.assign(state, {
        id: preview.id,
        skipUpdate: true,
        isUpdating: true,
        initialData: preview.image,
      });

      this.setImageData(preview.image);
    },

    sendPreview() {
      const $image = $('#previewImage');
      const { id, imageData: image } = state;
      if (this.isDisabled) return id;

      if (!id || !$image.length || $image.hasClass('hidden')) return '';

      const { naturalWidth: width, naturalHeight: height } = $image[0];
      const preview = JSON.stringify({ id, image, width, height });
      BrowserAutomationStudio_SendPreview(preview);
      return id;
    },

    deleteImage() {
      this.setImageData(defaults.initialData);
    },

    restoreImage() {
      this.setImageData(state.initialData);
    },

    waitForUpdate() {
      return new Promise((resolve) => {
        if (this.isDisabled || !state.isUpdating) return resolve();

        emitter.on('update', function handler() {
          emitter.off('update', handler);
          resolve();
        });
      });
    },

    async setImageData(imageData) {
      if (this.isDisabled) return;

      const isEmpty = imageData === EMPTY_DATA;
      const isValid =
        isEmpty ||
        (await new Promise((resolve) => {
          image.onload = () => resolve(true);
          image.onerror = () => resolve(false);
          image.src = `data:image/png;base64,${imageData}`;
        }));

      const { initialData } = state;
      const isHidden = !isValid || isEmpty;
      const actualData = isHidden
        ? imageData
        : await PreviewUtils.cropImage(
            image,
            cropSettings.maxWidth,
            cropSettings.maxHeight,
            cropSettings.cropThreshold
          );
      const [format, base64] = isHidden ? ['gif', EMPTY_DATA] : ['png', actualData];

      $('.preview-button').prop('disabled', false);
      $('#deletePreview').toggleClass('hidden', isEmpty);
      $('#ElementPreviewSettingsCheck + label > i').addClass('hidden');
      $('#restorePreview').toggleClass('hidden', !_GobalModel.get('isedit') || actualData === initialData);
      $('#previewImage').toggleClass('hidden', isHidden).attr('src', `data:image/${format};base64,${base64}`);

      Object.assign(state, {
        isUpdating: false,
        id: isHidden ? '' : md5(actualData),
        imageData: isValid ? actualData : EMPTY_DATA,
      });
      emitter.emit('update');
    },

    updateImage(path = state.lastPath) {
      if (this.isDisabled || !$('#previewImage').length || window.BrowserAutomationStudio_IsUpdatingAllActions) return;
      if (!_SelectorModel.get('is_image') && (parseInt($('#PathCount').text()) || 0) > 0) {
        if (!path.length) return;
        const selector = JSON.parse(path);
        Object.assign(state, { lastPath: path, isUpdating: true });

        if (selector.length && !['position', 'at'].includes(selector[selector.length - 2])) {
          selector.push('at', '0');
        }

        $('.preview-button').prop('disabled', true);
        $('#ElementPreviewSettingsCheck + label > i').removeClass('hidden');
        return BrowserAutomationStudio_UpdatePreview(JSON.stringify(selector));
      } else if (state.skipUpdate) {
        state.skipUpdate = false;
      } else {
        this.deleteImage();
      }
    },

    deferUpdate: _.debounce((path) => PreviewModel.updateImage(path), 200),
  };
})();
