import cloneDeep from 'lodash.clonedeep'
import {CloudinaryImage} from "@cloudinary/url-gen/assets/CloudinaryImage";
import {Plugin, HtmlPluginState} from "../types";
import {PLACEHOLDER_IMAGE_OPTIONS, singleTransparentPixel} from '../utils/internalConstants';
import {PlaceholderMode} from '../types';
import {isBrowser} from "../utils/isBrowser";
import {Action} from "@cloudinary/url-gen/internal/Action";
import {isImage} from "../utils/isImage";

/**
 * @namespace
 * @description Displays a placeholder image until the original image loads.
 * @param mode {PlaceholderMode} The type of placeholder image to display. Possible modes: 'vectorize' | 'pixelate' | 'blur' | 'predominant-color'. Default: 'vectorize'.
 * @return {Plugin}
 * @example <caption>NOTE: The following is in React. For further examples, see the Packages tab.</caption>
 * <AdvancedImage cldImg={img} plugins={[placeholder({mode: 'blur'})]} />
 */
export function placeholder({mode='vectorize'}:{mode?: string}={}): Plugin{
  return placeholderPlugin.bind(null, mode);
}

/**
 * @description Placeholder plugin
 * @param mode {PlaceholderMode} The type of placeholder image to display. Possible modes: 'vectorize' | 'pixelate' | 'blur' | 'predominant-color'. Default: 'vectorize'.
 * @param element {HTMLImageElement} The image element.
 * @param pluginCloudinaryImage {CloudinaryImage}
 * @param htmlPluginState {htmlPluginState} Holds cleanup callbacks and event subscriptions.
 */
function placeholderPlugin(mode: PlaceholderMode, element: HTMLImageElement, pluginCloudinaryImage: CloudinaryImage, htmlPluginState: HtmlPluginState): Promise<void | string> {
  // @ts-ignore
  // If we're using an invalid mode, we default to vectorize
  if(!PLACEHOLDER_IMAGE_OPTIONS[mode]){
    mode = 'vectorize'
  }

  // A placeholder mode maps to an array of transformations
  const PLACEHOLDER_ACTIONS = PLACEHOLDER_IMAGE_OPTIONS[mode].actions;

  // Before proceeding, clone the original image
  // We clone because we don't want to pollute the state of the image
  // Future renders (after the placeholder is loaded) should not load placeholder transformations
  const placeholderClonedImage = cloneDeep(pluginCloudinaryImage);

  //appends a placeholder transformation on the clone
  // @ts-ignore
  PLACEHOLDER_ACTIONS.forEach(function(transformation:Action){
    placeholderClonedImage.addAction(transformation);
  });

  if(!isBrowser()) {
    // in SSR, we copy the transformations of the clone to the user provided CloudinaryImage
    // We return here, since we don't have HTML elements to work with.
    pluginCloudinaryImage.transformation = placeholderClonedImage.transformation;
    return;
  }

  // Client side rendering, if an image was not provided we don't perform any action
  if(!isImage(element)) return;

  // Set the SRC of the imageElement to the URL of the placeholder Image
  element.src = placeholderClonedImage.toURL();

  //Fallback, if placeholder errors, load a single transparent pixel
  element.onerror = () => {
    element.src = singleTransparentPixel;
  };

  /*
   * This plugin loads two images:
   * - The first image is loaded as a placeholder
   * - The second image is loaded after the placeholder is loaded
   *
   * Placeholder image loads first. Once it loads, the promise is resolved and the
   * larger image will load. Once the larger image loads, promised and plugin is resolved.
   */
  return new Promise((resolve: any) => {
    element.onload = () => {
      resolve();
    };
  }).then(()=>{
    return new Promise((resolve: any) => {
      htmlPluginState.cleanupCallbacks.push(()=>{
        element.src = singleTransparentPixel;
        resolve('canceled');
      });
      // load image once placeholder is done loading
      const largeImage = new Image();
      largeImage.src = pluginCloudinaryImage.toURL();
      largeImage.onload = () => {
        resolve();
      };

      // image does not load, resolve
      largeImage.onerror = () => {
        resolve();
      };
    });
  });
}