text: import $ from 'jquery';
  import Component from '@ember/component';
  import EmberObject, {computed, set} from '@ember/object';
  import {
      IMAGE_EXTENSIONS,
      IMAGE_MIME_TYPES
  } from 'ghost-admin/components/gh-image-uploader';
  import {utils as ghostHelperUtils} from '@tryghost/helpers';
  import {htmlSafe} from '@ember/template';
  import {isEmpty} from '@ember/utils';
  import {run} from '@ember/runloop';
  import {inject as service} from '@ember/service';
  
  const MAX_IMAGES = 9;
  const MAX_PER_ROW = 3;
  const {countWords} = ghostHelperUtils;
  
  export default Component.extend({
      koenigDragDropHandler: service(),
  
      // attrs
      files: null,
      images: null,
      payload: null,
      isSelected: false,
      isEditing: false,
      imageExtensions: IMAGE_EXTENSIONS,
      imageMimeTypes: IMAGE_MIME_TYPES,
  
      // properties
      errorMessage: null,
      handlesDragDrop: true,
  
      _dragDropContainer: null,
  
      // closure actions
      selectCard() { },
      deselectCard() { },
      editCard() { },
      saveCard() { },
      deleteCard() { },
      moveCursorToNextSection() { },
      moveCursorToPrevSection() { },
      addParagraphAfterCard() { },
      registerComponent() { },
  
      counts: computed('payload.{caption,payload.images.[]}', function () {
          let wordCount = 0;
          let imageCount = this.payload.images.length;
  
          if (this.payload.caption) {
              wordCount += countWords(this.payload.caption);
          }
  
          return {wordCount, imageCount};
      }),
  
      toolbar: computed('images.[]', function () {
          if (isEmpty(this.images)) {
              return false;
          }
  
          return {
              items: [{
                  title: 'Add images',
                  icon: 'koenig/kg-add',
                  iconClass: 'fill-white',
                  action: run.bind(this, this._triggerFileDialog)
              }]
          };
      }),
  
      imageRows: computed('images.@each.{src,previewSrc,width,height,row}', function () {
          let rows = [];
          let noOfImages = this.images.length;
          // 3 images per row unless last row would have a single image in which
          // case the last 2 rows will have 2 images
          let maxImagesInRow = function (idx) {
              return noOfImages > 1 && (noOfImages % 3 === 1) && (idx === (noOfImages - 2));
          };
  
          this.images.forEach((image, idx) => {
              let row = image.row;
              let classes = [];
              let overlayClasses = [];
  
              // start a new display row if necessary
              if (maxImagesInRow(idx)) {
                  row = row + 1;
              }
  
              // apply classes to the image containers
              if (!rows[row]) {
                  // first image in row
                  rows[row] = [];
                  classes.push('pr2');
                  overlayClasses.push('mr2');
              } else if (((idx + 1) % 3 === 0) || maxImagesInRow(idx + 1) || idx + 1 === noOfImages) {
                  // last image in row
                  classes.push('pl2');
                  overlayClasses.push('ml2');
              } else {
                  // middle of row
                  classes.push('pl2', 'pr2');
                  overlayClasses.push('ml2', 'mr2');
              }
  
              if (row > 0) {
                  classes.push('mt4');
              }
  
              let styledImage = Object.assign({}, image);
              let aspectRatio = (image.width || 1) / (image.height || 1);
              styledImage.style = htmlSafe(`flex: ${aspectRatio} 1 0%`);
              styledImage.classes = htmlSafe(classes.join(' '));
              styledImage.overlayClasses = htmlSafe(overlayClasses.join(' '));
  
              rows[row].push(styledImage);
          });
  
          return rows;
      }),
  
      init() {
          this._super(...arguments);
  
          if (!this.payload || isEmpty(this.payload.images)) {
              this._updatePayloadAttr('images', []);
          }
  
          this._buildImages();
  
          this.registerComponent(this);
      },
  
      willDestroyElement() {
          this._super(...arguments);
  
          if (this._dragDropContainer) {
              this._dragDropContainer.destroy();
          }
      },
  
      actions: {
          addImage(file) {
              let count = this.images.length + 1;
              let row = Math.ceil(count / MAX_PER_ROW) - 1;
  
              let image = this._readDataFromImageFile(file);
              image.row = row;
              this.images.pushObject(image);
          },
  
          setImageSrc(uploadResult) {
              let image = this.images.findBy('fileName', uploadResult.fileName);
  
              image.set('src', uploadResult.url);
  
              this._buildAndSaveImagesPayload();
          },
  
          setFiles(files) {
              this._startUpload(files);
          },
  
          deleteImage(image) {
              let localImage = this.images.findBy('fileName', image.fileName);
              this.images.removeObject(localImage);
              this._recalculateImageRows();
              this._buildAndSaveImagesPayload();
          },
  
          updateCaption(caption) {
              this._updatePayloadAttr('caption', caption);
          },
  
          triggerFileDialog(event) {
              this._triggerFileDialog(event);
          },
  
          uploadFailed(uploadResult) {
              let image = this.images.findBy('fileName', uploadResult.fileName);
              this.images.removeObject(image);
  
              this._buildAndSaveImagesPayload();
              let fileName = (uploadResult.fileName.length > 20) ? `${uploadResult.fileName.substr(0, 20)}...` : uploadResult.fileName;
              this.set('errorMessage', `${fileName} failed to upload`);
          },
          handleErrors(errors) {
              let errorMssg = ((errors[0] && errors[0].message)) || 'Some images failed to upload';
              this.set('errorMessage', errorMssg);
          },
  
          clearErrorMessage() {
              this.set('errorMessage', null);
          },
  
          didSelect() {
              if (this._dragDropContainer) {
                  // add a delay when enabling reorder drag/drop so that the card
                  // must be selected before a reorder drag can be initiated
                  // - allows for cards to be drag and dropped themselves
                  run.later(this, function () {
                      if (!this.isDestroyed && !this.isDestroying) {
                          this._dragDropContainer.enableDrag();
                      }
                  }, 100);
              }
          },
  
          didDeselect() {
              if (this._dragDropContainer) {
                  this._dragDropContainer.disableDrag();
              }
          }
      },
  
      // Ember event handlers ----------------------------------------------------
  
      dragOver(event) {
          if (!event.dataTransfer) {
              return;
          }
  
          // this is needed to work around inconsistencies with dropping files
          // from Chrome's downloads bar
          if (navigator.userAgent.indexOf('Chrome') > -1) {
              let eA = event.dataTransfer.effectAllowed;
              event.dataTransfer.dropEffect = (eA === 'move' || eA === 'linkMove') ? 'move' : 'copy';
          }
  
          event.stopPropagation();
          event.preventDefault();
  
          this.set('isDraggedOver', true);
      },
  
      dragLeave(event) {
          event.preventDefault();
          this.set('isDraggedOver', false);
      },
  
      drop(event) {
          event.preventDefault();
          this.set('isDraggedOver', false);
  
          if (event.dataTransfer.files) {
              this._startUpload(event.dataTransfer.files);
          }
      },
  
      // Private methods ---------------------------------------------------------
  
      _recalculateImageRows() {
          this.images.forEach((image, idx) => {
              image.set('row', Math.ceil((idx + 1) / MAX_PER_ROW) - 1);
          });
      },
  
      _startUpload(files = []) {
          let currentCount = this.images.length;
          let allowedCount = (MAX_IMAGES - currentCount);
  
          let strippedFiles = Array.prototype.slice.call(files, 0, allowedCount);
          if (strippedFiles.length < files.length) {
              this.set('errorMessage', 'Galleries are limited to 9 images');
          }
          this.set('files', strippedFiles);
      },
  
      _readDataFromImageFile(file) {
          let url = URL.createObjectURL(file);
          let image = EmberObject.create({
              fileName: file.name,
              previewSrc: url
          });
  
          let imageElem = new Image();
          imageElem.onload = () => {
              // update current display images
              image.set('width', imageElem.naturalWidth);
              image.set('height', imageElem.naturalHeight);
  
              // ensure width/height makes it into the payload images
              this._buildAndSaveImagesPayload();
          };
          imageElem.src = url;
  
          return image;
      },
  
      _buildAndSaveImagesPayload() {
          let payloadImages = [];
  
          let isValidImage = image => image.fileName
                  && image.src
                  && image.width
                  && image.height;
  
          this.images.forEach((image, idx) => {
              if (isValidImage(image)) {
                  let payloadImage = Object.assign({}, image, {previewSrc: undefined});
                  payloadImage.row = Math.ceil((idx + 1) / MAX_PER_ROW) - 1;
  
                  payloadImages.push(payloadImage);
              }
          });
  
          this._updatePayloadAttr('images', payloadImages);
      },
  
      _buildImages() {
          this.images = this.payload.images.map(image => EmberObject.create(image));
          this._registerOrRefreshDragDropHandler();
      },
  
      _updatePayloadAttr(attr, value) {
          let payload = this.payload;
          let save = this.saveCard;
  
          set(payload, attr, value);
  
          // update the mobiledoc and stay in edit mode
          save(payload, false);
  
          this._registerOrRefreshDragDropHandler();
      },
  
      _triggerFileDialog(event) {
          let target = event && event.target || this.element;
  
          // simulate click to open file dialog
          // using jQuery because IE11 doesn't support MouseEvent
          $(target)
              .closest('.__mobiledoc-card')
              .find('input[type="file"]')
              .click();
      },
  
      // - rename container so that it's more explicit when we have an initial file
      //   drop container vs a drag reorder+file drop container?
      _registerOrRefreshDragDropHandler() {
          if (this._dragDropContainer) {
              run.scheduleOnce('afterRender', this, this._refreshDragDropContainer);
          } else {
              run.scheduleOnce('afterRender', this, this._registerDragDropContainer);
          }
      },
  
      _refreshDragDropContainer() {
          const galleryElem = this.element.querySelector('[data-gallery]');
  
          // gallery element can change when switching from placeholder to gallery
          // make sure the DnD container is updated to match
          if (this._dragDropContainer.element !== galleryElem) {
              this._dragDropContainer.destroy();
              this._registerDragDropContainer();
          } else {
              this._dragDropContainer.refresh();
          }
  
          if (!isEmpty(this.images) && !this._dragDropContainer.isDragEnabled) {
              this._dragDropContainer.enableDrag();
          }
      },
  
      _registerDragDropContainer() {
          const galleryElem = this.element.querySelector('[data-gallery]');
          if (galleryElem) {
              this._dragDropContainer = this.koenigDragDropHandler.registerContainer(
                  galleryElem,
                  {
                      draggableSelector: '[data-image]',
                      droppableSelector: '[data-image]',
                      isDragEnabled: !isEmpty(this.images),
                      onDragStart: run.bind(this, this._dragStart),
                      onDragEnd: run.bind(this, this._dragEnd),
                      onDragEnterContainer: run.bind(this, this._dragEnter),
                      onDragLeaveContainer: run.bind(this, this._dragLeave),
                      getDraggableInfo: run.bind(this, this._getDraggableInfo),
                      getIndicatorPosition: run.bind(this, this._getDropIndicatorPosition),
                      onDrop: run.bind(this, this._onDrop),
                      onDropEnd: run.bind(this, this._onDropEnd)
                  }
              );
          }
      },
  
      _dragStart(draggableInfo) {
          this.element.querySelector('figure').classList.remove('kg-card-selected');
  
          // enable dropping when an image is dragged in from outside of this card
          let isImageDrag = draggableInfo.type === 'image' || draggableInfo.cardName === 'image';
          if (isImageDrag && draggableInfo.payload.src && this.images.length !== MAX_IMAGES) {
              this._dragDropContainer.enableDrag();
          }
      },
  
      _dragEnd() {
          if (this.isSelected) {
              this.element.querySelector('figure').classList.add('kg-card-selected');
          } else {
              this._dragDropContainer.disableDrag();
          }
  
          if (this.isDraggedOver) {
              this.set('isDraggedOver', false);
          }
      },
  
      _dragEnter() {
          if (this.images.length === 0) {
              this.set('isDraggedOver', true);
          }
      },
  
      _dragLeave() {
          this.set('isDraggedOver', false);
      },
  
      _getDraggableInfo(draggableElement) {
          let src = draggableElement.querySelector('img').getAttribute('src');
          let image = this.images.findBy('src', src) || this.images.findBy('previewSrc', src);
          let payload = image && image.getProperties('fileName', 'src', 'row', 'width', 'height', 'caption');
  
          if (image) {
              return {
                  type: 'image',
                  payload
              };
          }
  
          return {};
      },
  
      _onDrop(draggableInfo/*, droppableElem, position*/) {
          // do not allow dropping of non-images
          if (draggableInfo.type !== 'image' && draggableInfo.cardName !== 'image') {
              return false;
          }
  
          let {insertIndex} = draggableInfo;
          let droppables = Array.from(this.element.querySelectorAll('[data-image]'));
          let draggableIndex = droppables.indexOf(draggableInfo.element);
  
          if (!this.images.length) {
              insertIndex = 0;
          }
  
          if (this._isDropAllowed(draggableIndex, insertIndex)) {
              if (draggableIndex === -1) {
                  // external image being added
                  let {payload} = draggableInfo;
                  let img = draggableInfo.element.querySelector(`img[src="${payload.src}"]`);
  
                  // image card payloads may not have all of the details we need but we can fill them in
                  payload.width = payload.width || img.naturalWidth;
                  payload.height = payload.height || img.naturalHeight;
                  if (!payload.fileName) {
                      let url = new URL(payload.src || img.src);
                      let fileName = url.pathname.match(/\/([^/]*)$/)[1];
                      payload.fileName = fileName;
                  }
  
                  this.images.insertAt(insertIndex, EmberObject.create(payload));
              } else {
                  // internal image being re-ordered
                  let draggedImage = this.images.findBy('src', draggableInfo.payload.src);
                  let accountForRemoval = draggableIndex < insertIndex && insertIndex ? -1 : 0;
                  this.images.removeObject(draggedImage);
                  this.images.insertAt(insertIndex + accountForRemoval, draggedImage);
              }
  
              this._recalculateImageRows();
              this._buildAndSaveImagesPayload();
              this._dragDropContainer.refresh();
  
              this._skipOnDragEnd = true;
              return true;
          }
  
          return false;
      },
  
      // if an image is dragged out of a gallery we need to remove it
      _onDropEnd(draggableInfo, success) {
          if (this._skipOnDragEnd || !success) {
              this._skipOnDragEnd = false;
              return;
          }
  
          let image = this.images.findBy('src', draggableInfo.payload.src);
          if (image) {
              this.images.removeObject(image);
              this._recalculateImageRows();
              this._buildAndSaveImagesPayload();
              this._dragDropContainer.refresh();
          }
      },
  
      // returns {
      //   direction: 'horizontal' TODO: use a constant?
      //   position: 'left'/'right' TODO: use constants?
      //   beforeElems: array of elems to left of indicator
      //   afterElems: array of elems to right of indicator
      //   droppableIndex:
      // }
      _getDropIndicatorPosition(draggableInfo, droppableElem, position) {
          // do not allow dropping of non-images
          if (draggableInfo.type !== 'image' && draggableInfo.cardName !== 'image') {
              return false;
          }
  
          let row = droppableElem.closest('[data-row]');
          let droppables = Array.from(this.element.querySelectorAll('[data-image]'));
          let draggableIndex = droppables.indexOf(draggableInfo.element);
          let droppableIndex = droppables.indexOf(droppableElem);
  
          if (row && this._isDropAllowed(draggableIndex, droppableIndex, position)) {
              let rowImages = Array.from(row.querySelectorAll('[data-image]'));
              let rowDroppableIndex = rowImages.indexOf(droppableElem);
              let insertIndex = droppableIndex;
              let beforeElems = [];
              let afterElems = [];
  
              rowImages.forEach((image, index) => {
                  if (index < rowDroppableIndex) {
                      beforeElems.push(image);
                  }
  
                  if (index === rowDroppableIndex) {
                      if (position.match(/left/)) {
                          afterElems.push(image);
                      } else {
                          beforeElems.push(image);
                      }
                  }
  
                  if (index > rowDroppableIndex) {
                      afterElems.push(image);
                  }
              });
  
              if (position.match(/right/)) {
                  insertIndex += 1;
              }
  
              return {
                  direction: 'horizontal',
                  position: position.match(/left/) ? 'left' : 'right',
                  beforeElems,
                  afterElems,
                  insertIndex
              };
          } else {
              return false;
          }
      },
  
      // we don't allow an image to be dropped where it would end up in the
      // same position within the gallery
      _isDropAllowed(draggableIndex, droppableIndex, position = '') {
          // external images can always be dropped
          if (draggableIndex === -1) {
              return true;
          }
  
          // can't drop on itself or when droppableIndex doesn't exist
          if (draggableIndex === droppableIndex || typeof droppableIndex === 'undefined') {
              return false;
          }
  
          // account for dropping at beginning or end of a row
          if (position.match(/left/)) {
              droppableIndex -= 1;
          }
  
          if (position.match(/right/)) {
              droppableIndex += 1;
          }
  
          return droppableIndex !== draggableIndex;
      }
  });
  