import {CloudinaryImage} from "@cloudinary/url-gen/assets/CloudinaryImage";
import {Plugin, HtmlPluginState, BaseAnalyticsOptions, AnalyticsOptions, PluginResponse} from "../types";
import {scale} from "@cloudinary/url-gen/actions/resize";
import debounce from 'lodash.debounce';
import {isNum} from '../utils/isNum';
import {isBrowser} from "../utils/isBrowser";
import {Action} from "@cloudinary/url-gen/internal/Action";
import {isImage} from "../utils/isImage";
import {getAnalyticsOptions} from '../utils/analytics'

/**
 * @namespace
 * @description Updates the src with the size of the parent element and triggers a resize event for
 * subsequent resizing.
 * @param steps {number | number[]} The step size in pixels or an array of image widths in pixels.
 * @return {Plugin}
 * @example <caption>NOTE: The following is in React. For further examples, see the Packages tab.</caption>
 * <AdvancedImage cldImg={img} plugins={[responsive({steps: [800, 1000, 1400]})]} />
 */
export function responsive({steps}:{steps?: number | number[]}={}): Plugin{
  return responsivePlugin.bind(null, steps);
}

/**
 * @description Responsive plugin
 * @param steps {number | number[]} The step size in pixels.
 * @param element {HTMLImageElement} The image element
 * @param responsiveImage {CloudinaryImage}
 * @param htmlPluginState {HtmlPluginState} holds cleanup callbacks and event subscriptions
 * @param analyticsOptions {BaseAnalyticsOptions} analytics options for the url to be created
 */
function responsivePlugin(steps?: number | number[], element?:HTMLImageElement, responsiveImage?: CloudinaryImage, htmlPluginState?: HtmlPluginState, baseAnalyticsOptions?: BaseAnalyticsOptions): Promise<PluginResponse> | boolean {

  if(!isBrowser()) return true;

  if(!isImage(element)) return;

  return new Promise((resolve)=>{
    htmlPluginState.cleanupCallbacks.push(()=>{
      window.removeEventListener("resize", resizeRef);
      resolve('canceled');
    });

    const analyticsOptions = getAnalyticsOptions(baseAnalyticsOptions, {responsive: true});

    // Use a tagged generic action that can be later searched and replaced.
    responsiveImage.addAction(new Action().setActionTag('responsive'));
    // Immediately run the resize plugin, ensuring that first render gets a responsive image.
    onResize(steps, element, responsiveImage, analyticsOptions);

    let resizeRef: any;
    htmlPluginState.pluginEventSubscription.push(()=>{
      window.addEventListener('resize', resizeRef = debounce(()=>{
        onResize(steps, element, responsiveImage, analyticsOptions);
      }, 100));
    });
    resolve({responsive: true});
  });
}

/**
 * On resize updates image src
 * @param steps {number | number[]} The step size in pixels.
 * | number[] A set of image sizes in pixels.
 * @param element {HTMLImageElement} The image element
 * @param responsiveImage {CloudinaryImage}
 * @param analyticsOptions {AnalyticsOptions} analytics options for the url to be created
 */
function onResize(steps?: number | number[], element?:HTMLImageElement, responsiveImage?: CloudinaryImage, analyticsOptions?: AnalyticsOptions){
  updateByContainerWidth(steps, element, responsiveImage);
  element.src = responsiveImage.toURL(analyticsOptions);
}

/**
 * Updates the responsiveImage by container width.
 * @param steps {number | number[]} The step size in pixels.
 * | number[] A set of image sizes in pixels.
 * @param element {HTMLImageElement} The image element
 * @param responsiveImage {CloudinaryImage}
 */
function updateByContainerWidth(steps?: number | number[], element?:HTMLImageElement, responsiveImage?: CloudinaryImage){
  // Default value for responsiveImgWidth, used when no steps are passed.
  let responsiveImgWidth = element.parentElement.clientWidth;

  if(isNum(steps)){
    const WIDTH_INTERVALS = steps as number;
    // We need to force the container width to be intervals of max width.
    responsiveImgWidth = Math.ceil(responsiveImgWidth / WIDTH_INTERVALS ) * WIDTH_INTERVALS;

  } else if(Array.isArray(steps)){
    responsiveImgWidth = steps.reduce((prev, curr) =>{
      return (Math.abs(curr - responsiveImgWidth) < Math.abs(prev - responsiveImgWidth) ? curr : prev);
    });
  }

  responsiveImage.transformation.actions.forEach((action, index) => {
    if (action instanceof Action && action.getActionTag() === 'responsive') {
      responsiveImage.transformation.actions[index]  = scale(responsiveImgWidth).setActionTag('responsive');
    }
  });
}