import { Arr } from '@ephox/katamari';

import * as DragImage from './DragImage';
import * as Event from './Event';
import * as Files from './Files';
import * as Items from './Items';
import * as Mode from './Mode';

type DropEffect = DataTransfer['dropEffect'];
type EffectAllowed = DataTransfer['effectAllowed'];

const validDropEffects: DropEffect[] = [ 'none', 'copy', 'link', 'move' ];
const validEffectAlloweds: EffectAllowed[] = [ 'none', 'copy', 'copyLink', 'copyMove', 'link', 'linkMove', 'move', 'all', 'uninitialized' ];

const createDataTransfer = (): DataTransfer => {
  const dataTransferImpl = new window.DataTransfer();
  let dropEffect: DropEffect = 'move';
  let effectAllowed: EffectAllowed = 'all';

  const dataTransfer: DataTransfer = {
    get dropEffect() {
      return dropEffect;
    },

    set dropEffect(effect: DataTransfer['dropEffect']) {
      if (Arr.contains(validDropEffects, effect)) {
        dropEffect = effect;
      }
    },

    get effectAllowed() {
      return effectAllowed;
    },

    set effectAllowed(allowed: DataTransfer['effectAllowed']) {
      // TINY-9601: Only allow setting effectAllowed to a valid value in a dragstart event
      // https://developer.mozilla.org/en-US/docs/Web/API/DataTransfer/effectAllowed
      if (Event.isInDragStartEvent(dataTransfer) && Arr.contains(validEffectAlloweds, allowed)) {
        effectAllowed = allowed;
      }
    },

    get items() {
      return Items.normalizeItems(dataTransfer, dataTransferImpl.items);
    },

    get files() {
      if (Mode.isInProtectedMode(dataTransfer)) {
        return Files.createEmptyFileList();
      } else {
        return dataTransferImpl.files;
      }
    },

    get types() {
      return dataTransferImpl.types;
    },

    setDragImage: (image: Element, x: number, y: number): void => {
      if (Mode.isInReadWriteMode(dataTransfer)) {
        DragImage.setDragImage(dataTransfer, { image, x, y });
        dataTransferImpl.setDragImage(image, x, y);
      }
    },

    getData: (format: string): string => {
      if (Mode.isInProtectedMode(dataTransfer)) {
        return '';
      } else {
        return dataTransferImpl.getData(format);
      }
    },

    setData: (format: string, data: string): void => {
      if (Mode.isInReadWriteMode(dataTransfer)) {
        dataTransferImpl.setData(format, data);
      }
    },

    clearData: (format?: string): void => {
      if (Mode.isInReadWriteMode(dataTransfer)) {
        dataTransferImpl.clearData(format);
      }
    }
  };

  Mode.setReadWriteMode(dataTransfer);

  return dataTransfer;
};

const cloneDataTransfer = (original: DataTransfer): DataTransfer => {
  // Create new DataTransfer object to ensure scope is not shared between original and clone
  const clone = createDataTransfer();

  const originalMode = Mode.getMode(original);
  // Set original to read-only to ensure data can be copied
  Mode.setReadOnlyMode(original);

  // Set clone event to dragstart to ensure effectAllowed can be set
  Event.setDragstartEvent(clone);

  clone.dropEffect = original.dropEffect;
  clone.effectAllowed = original.effectAllowed;
  DragImage.getDragImage(original).each((imageData) => clone.setDragImage(imageData.image, imageData.x, imageData.y));

  Arr.each(original.types, (type) => {
    if (type !== 'Files') {
      clone.setData(type, original.getData(type));
    }
  });

  Arr.each(original.files, (file) => clone.items.add(file));

  Event.getEvent(original).each((type) => {
    Event.setEvent(clone, type);
  });

  originalMode.each((mode) => {
    // Reset original mode since it was set to read-only earlier
    Mode.setMode(original, mode);
    Mode.setMode(clone, mode);
  });

  return clone;
};

const getDragImage = DragImage.getDragImage;

export { createDataTransfer, cloneDataTransfer, getDragImage };
