import { extend } from "../../../shared/utils/extend.js";

const buildURLQuery = (src, obj) => {
  const url = new URL(src);
  const params = new URLSearchParams(url.search);

  let rez = new URLSearchParams();

  for (const [key, value] of [...params, ...Object.entries(obj)]) {
    // Youtube
    if (key === "t") {
      rez.set("start", parseInt(value));
    } else {
      rez.set(key, value);
    }
  }

  // Convert to 'foo=1&bar=2&baz=3'
  rez = rez.toString();

  // Vimeo
  // https://vimeo.zendesk.com/hc/en-us/articles/360000121668-Starting-playback-at-a-specific-timecode
  let matches = src.match(/#t=((.*)?\d+s)/);

  if (matches) {
    rez += `#t=${matches[1]}`;
  }

  return rez;
};

const defaults = {
  // General options for any video content (Youtube, Vimeo, HTML5 video)
  video: {
    autoplay: true,
    ratio: 16 / 9,
  },
  // Youtube embed parameters
  youtube: {
    autohide: 1,
    fs: 1,
    rel: 0,
    hd: 1,
    wmode: "transparent",
    enablejsapi: 1,
    html5: 1,
  },
  // Vimeo embed parameters
  vimeo: {
    hd: 1,
    show_title: 1,
    show_byline: 1,
    show_portrait: 0,
    fullscreen: 1,
  },
  // HTML5 video parameters
  html5video: {
    tpl: `<video class="fancybox__html5video" playsinline controls controlsList="nodownload" poster="{{poster}}">
  <source src="{{src}}" type="{{format}}" />Sorry, your browser doesn't support embedded videos.</video>`,
    format: "",
  },
};

export class Html {
  constructor(fancybox) {
    this.fancybox = fancybox;

    for (const methodName of [
      "onInit",
      "onReady",

      "onCreateSlide",
      "onRemoveSlide",

      "onSelectSlide",
      "onUnselectSlide",

      "onRefresh",

      // For communication with iframed video (youtube/vimeo)
      "onMessage",
    ]) {
      this[methodName] = this[methodName].bind(this);
    }

    this.events = {
      init: this.onInit,
      ready: this.onReady,

      "Carousel.createSlide": this.onCreateSlide,
      "Carousel.removeSlide": this.onRemoveSlide,

      "Carousel.selectSlide": this.onSelectSlide,
      "Carousel.unselectSlide": this.onUnselectSlide,

      "Carousel.refresh": this.onRefresh,
    };
  }

  /**
   * Check if each gallery item has type when fancybox starts
   */
  onInit() {
    for (const slide of this.fancybox.items) {
      this.processType(slide);
    }
  }

  /**
   * Set content type for the slide
   * @param {Object} slide
   */
  processType(slide) {
    // Add support for `new Fancybox({items : [{html : 'smth'}]});`
    if (slide.html) {
      slide.src = slide.html;
      slide.type = "html";

      delete slide.html;

      return;
    }

    const src = slide.src || "";

    let type = slide.type || this.fancybox.options.type,
      rez = null;

    if (src && typeof src !== "string") {
      return;
    }

    if (
      (rez = src.match(
        /(?:youtube\.com|youtu\.be|youtube\-nocookie\.com)\/(?:watch\?(?:.*&)?v=|v\/|u\/|embed\/?)?(videoseries\?list=(?:.*)|[\w-]{11}|\?listType=(?:.*)&list=(?:.*))(?:.*)/i
      ))
    ) {
      const params = buildURLQuery(src, this.fancybox.option("Html.youtube"));
      const videoId = encodeURIComponent(rez[1]);

      slide.videoId = videoId;
      slide.src = `https://www.youtube-nocookie.com/embed/${videoId}?${params}`;
      slide.thumb = slide.thumb || `https://i.ytimg.com/vi/${videoId}/mqdefault.jpg`;
      slide.vendor = "youtube";

      type = "video";
    } else if ((rez = src.match(/^.+vimeo.com\/(?:\/)?([\d]+)(.*)?/))) {
      const params = buildURLQuery(src, this.fancybox.option("Html.vimeo"));
      const videoId = encodeURIComponent(rez[1]);

      slide.videoId = videoId;
      slide.src = `https://player.vimeo.com/video/${videoId}?${params}`;
      slide.vendor = "vimeo";

      type = "video";
    } else if (
      (rez = src.match(
        /(?:maps\.)?google\.([a-z]{2,3}(?:\.[a-z]{2})?)\/(?:(?:(?:maps\/(?:place\/(?:.*)\/)?\@(.*),(\d+.?\d+?)z))|(?:\?ll=))(.*)?/i
      ))
    ) {
      slide.src = `//maps.google.${rez[1]}/?ll=${(rez[2]
        ? rez[2] + "&z=" + Math.floor(rez[3]) + (rez[4] ? rez[4].replace(/^\//, "&") : "")
        : rez[4] + ""
      ).replace(/\?/, "&")}&output=${rez[4] && rez[4].indexOf("layer=c") > 0 ? "svembed" : "embed"}`;

      type = "map";
    } else if ((rez = src.match(/(?:maps\.)?google\.([a-z]{2,3}(?:\.[a-z]{2})?)\/(?:maps\/search\/)(.*)/i))) {
      slide.src = `//maps.google.${rez[1]}/maps?q=${rez[2].replace("query=", "q=").replace("api=1", "")}&output=embed`;

      type = "map";
    }

    // Guess content type
    if (!type) {
      if (src.charAt(0) === "#") {
        type = "inline";
      } else if ((rez = src.match(/\.(mp4|mov|ogv|webm)((\?|#).*)?$/i))) {
        type = "html5video";

        slide.format = slide.format || "video/" + (rez[1] === "ogv" ? "ogg" : rez[1]);
      } else if (src.match(/(^data:image\/[a-z0-9+\/=]*,)|(\.(jp(e|g|eg)|gif|png|bmp|webp|svg|ico)((\?|#).*)?$)/i)) {
        type = "image";
      } else if (src.match(/\.(pdf)((\?|#).*)?$/i)) {
        type = "pdf";
      }
    }

    slide.type = type || this.fancybox.option("defaultType", "image");

    if (type === "html5video" || type === "video") {
      slide.video = extend({}, this.fancybox.option("Html.video"), slide.video);

      if (slide._width && slide._height) {
        slide.ratio = parseFloat(slide._width) / parseFloat(slide._height);
      } else {
        slide.ratio = slide.ratio || slide.video.ratio || defaults.video.ratio;
      }
    }
  }

  /**
   * Start loading content when Fancybox is ready
   */
  onReady() {
    this.fancybox.Carousel.slides.forEach((slide) => {
      if (slide.$el) {
        this.setContent(slide);

        if (slide.index === this.fancybox.getSlide().index) {
          this.playVideo(slide);
        }
      }
    });
  }

  /**
   * Process `Carousel.createSlide` event to create image content
   * @param {Object} fancybox
   * @param {Object} carousel
   * @param {Object} slide
   */
  onCreateSlide(fancybox, carousel, slide) {
    if (this.fancybox.state !== "ready") {
      return;
    }

    this.setContent(slide);
  }

  /**
   * Retrieve and set slide content
   * @param {Object} slide
   */
  loadInlineContent(slide) {
    let $content;

    if (slide.src instanceof HTMLElement) {
      $content = slide.src;
    } else if (typeof slide.src === "string") {
      const tmp = slide.src.split("#", 2);
      const id = tmp.length === 2 && tmp[0] === "" ? tmp[1] : tmp[0];

      $content = document.getElementById(id);
    }

    if ($content) {
      if (slide.type === "clone" || $content.$placeHolder) {
        $content = $content.cloneNode(true);
        let attrId = $content.getAttribute("id");

        attrId = attrId ? `${attrId}--clone` : `clone-${this.fancybox.id}-${slide.index}`;

        $content.setAttribute("id", attrId);
      } else {
        const $placeHolder = document.createElement("div");
        $placeHolder.classList.add("fancybox-placeholder");
        $content.parentNode.insertBefore($placeHolder, $content);
        $content.$placeHolder = $placeHolder;
      }

      this.fancybox.setContent(slide, $content);
    } else {
      this.fancybox.setError(slide, "{{ELEMENT_NOT_FOUND}}");
    }
  }

  /**
   * Makes AJAX request and sets response as slide content
   * @param {Object} slide
   */
  loadAjaxContent(slide) {
    const fancybox = this.fancybox;
    const xhr = new XMLHttpRequest();

    fancybox.showLoading(slide);

    xhr.onreadystatechange = function () {
      if (xhr.readyState === XMLHttpRequest.DONE) {
        if (fancybox.state === "ready") {
          fancybox.hideLoading(slide);

          if (xhr.status === 200) {
            fancybox.setContent(slide, xhr.responseText);
          } else {
            fancybox.setError(slide, xhr.status === 404 ? "{{AJAX_NOT_FOUND}}" : "{{AJAX_FORBIDDEN}}");
          }
        }
      }
    };

    xhr.open("GET", slide.src);
    xhr.setRequestHeader("X-Requested-With", "XMLHttpRequest");
    xhr.send(slide.ajax || null);

    slide.xhr = xhr;
  }

  /**
   * Creates iframe as slide content, preloads if needed before displaying
   * @param {Object} slide
   */
  loadIframeContent(slide) {
    const fancybox = this.fancybox;
    const $iframe = document.createElement("iframe");

    $iframe.className = "fancybox__iframe";

    $iframe.setAttribute("id", `fancybox__iframe_${fancybox.id}_${slide.index}`);

    $iframe.setAttribute("allow", "autoplay; fullscreen");
    $iframe.setAttribute("scrolling", "auto");

    slide.$iframe = $iframe;

    if (slide.type !== "iframe" || slide.preload === false) {
      $iframe.setAttribute("src", slide.src);

      this.fancybox.setContent(slide, $iframe);

      this.resizeIframe(slide);

      return;
    }

    fancybox.showLoading(slide);

    const $content = document.createElement("div");
    $content.style.visibility = "hidden";

    this.fancybox.setContent(slide, $content);

    $content.appendChild($iframe);

    $iframe.onerror = () => {
      fancybox.setError(slide, "{{IFRAME_ERROR}}");
    };

    $iframe.onload = () => {
      fancybox.hideLoading(slide);

      let isFirstLoad = false;

      if (!$iframe.isReady) {
        $iframe.isReady = true;
        isFirstLoad = true;
      }

      if (!$iframe.src.length) {
        return;
      }

      $iframe.parentNode.style.visibility = "";

      this.resizeIframe(slide);

      if (isFirstLoad) {
        fancybox.revealContent(slide);
      }
    };

    $iframe.setAttribute("src", slide.src);
  }

  /**
   * Set CSS max/min width/height properties of the content to have the correct aspect ratio
   * @param {Object} slide
   */
  setAspectRatio(slide) {
    const $content = slide.$content;
    const ratio = slide.ratio;

    if (!$content) {
      return;
    }

    let width = slide._width;
    let height = slide._height;

    if (ratio || (width && height)) {
      Object.assign($content.style, {
        width: width && height ? "100%" : "",
        height: width && height ? "100%" : "",
        maxWidth: "",
        maxHeight: "",
      });

      let maxWidth = $content.offsetWidth;
      let maxHeight = $content.offsetHeight;

      width = width || maxWidth;
      height = height || maxHeight;

      // Resize to fit
      if (width > maxWidth || height > maxHeight) {
        let maxRatio = Math.min(maxWidth / width, maxHeight / height);

        width = width * maxRatio;
        height = height * maxRatio;
      }

      // Recheck ratio
      if (Math.abs(width / height - ratio) > 0.01) {
        if (ratio < width / height) {
          width = height * ratio;
        } else {
          height = width / ratio;
        }
      }

      Object.assign($content.style, {
        width: `${width}px`,
        height: `${height}px`,
      });
    }
  }

  /**
   * Adjust the width and height of the iframe according to the content dimensions, or defined sizes
   * @param {Object} slide
   */
  resizeIframe(slide) {
    const $iframe = slide.$iframe;

    if (!$iframe) {
      return;
    }

    let width_ = slide._width || 0;
    let height_ = slide._height || 0;

    if (width_ && height_) {
      slide.autoSize = false;
    }

    const $parent = $iframe.parentNode;
    const parentStyle = $parent.style;

    if (slide.preload !== false && slide.autoSize !== false) {
      try {
        const compStyles = window.getComputedStyle($parent),
          paddingX = parseFloat(compStyles.paddingLeft) + parseFloat(compStyles.paddingRight),
          paddingY = parseFloat(compStyles.paddingTop) + parseFloat(compStyles.paddingBottom);

        const document = $iframe.contentWindow.document,
          $html = document.getElementsByTagName("html")[0],
          $body = document.body;

        // Get rid of vertical scrollbar
        $body.style.overflow = "hidden";

        width_ = width_ || $html.scrollWidth + paddingX;

        parentStyle.width = `${width_}px`;

        $body.style.overflow = "";

        parentStyle.flex = "0 0 auto";
        parentStyle.height = `${$body.scrollHeight}px`;

        height_ = $html.scrollHeight + paddingY;
      } catch (error) {
        //
      }
    }

    if (width_ || height_) {
      const newStyle = {
        flex: "0 1 auto",
      };

      if (width_) {
        newStyle.width = `${width_}px`;
      }

      if (height_) {
        newStyle.height = `${height_}px`;
      }

      Object.assign(parentStyle, newStyle);
    }
  }

  /**
   * Process `Carousel.onRefresh` event,
   * trigger iframe autosizing and set content aspect ratio for each slide
   * @param {Object} fancybox
   * @param {Object} carousel
   */
  onRefresh(fancybox, carousel) {
    carousel.slides.forEach((slide) => {
      if (!slide.$el) {
        return;
      }

      if (slide.$iframe) {
        this.resizeIframe(slide);
      }

      if (slide.ratio) {
        this.setAspectRatio(slide);
      }
    });
  }

  /**
   * Process `Carousel.onCreateSlide` event to set content
   * @param {Object} fancybox
   * @param {Object} carousel
   * @param {Object} slide
   */
  setContent(slide) {
    if (!slide || slide.isDom) {
      return;
    }

    switch (slide.type) {
      case "html":
        this.fancybox.setContent(slide, slide.src);
        break;

      case "html5video":
        this.fancybox.setContent(
          slide,
          this.fancybox
            .option("Html.html5video.tpl")
            .replace(/\{\{src\}\}/gi, slide.src)
            .replace("{{format}}", slide.format || (slide.html5video && slide.html5video.format) || "")
            .replace("{{poster}}", slide.poster || slide.thumb || "")
        );

        break;

      case "inline":
      case "clone":
        this.loadInlineContent(slide);
        break;

      case "ajax":
        this.loadAjaxContent(slide);
        break;

      case "pdf":
      case "video":
      case "map":
        slide.preload = false;

      case "iframe":
        this.loadIframeContent(slide);

        break;
    }

    if (slide.ratio) {
      this.setAspectRatio(slide);
    }
  }

  /**
   * Process `Carousel.onSelectSlide` event to start video
   * @param {Object} fancybox
   * @param {Object} carousel
   * @param {Object} slide
   */
  onSelectSlide(fancybox, carousel, slide) {
    if (fancybox.state === "ready") {
      this.playVideo(slide);
    }
  }

  /**
   * Attempts to begin playback of the media
   * @param {Object} slide
   */
  playVideo(slide) {
    if (slide.type === "html5video" && slide.video.autoplay) {
      try {
        const $video = slide.$el.querySelector("video");

        if ($video) {
          const promise = $video.play();

          if (promise !== undefined) {
            promise
              .then(() => {
                // Autoplay started
              })
              .catch((error) => {
                // Autoplay was prevented.
                $video.muted = true;
                $video.play();
              });
          }
        }
      } catch (err) {}
    }

    if (slide.type !== "video" || !(slide.$iframe && slide.$iframe.contentWindow)) {
      return;
    }

    // This function will be repeatedly called to check
    // if video iframe has been loaded to send message to start the video
    const poller = () => {
      if (slide.state === "done" && slide.$iframe && slide.$iframe.contentWindow) {
        let command;

        if (slide.$iframe.isReady) {
          if (slide.video && slide.video.autoplay) {
            if (slide.vendor == "youtube") {
              command = {
                event: "command",
                func: "playVideo",
              };
            } else {
              command = {
                method: "play",
                value: "true",
              };
            }
          }

          if (command) {
            slide.$iframe.contentWindow.postMessage(JSON.stringify(command), "*");
          }

          return;
        }

        if (slide.vendor === "youtube") {
          command = {
            event: "listening",
            id: slide.$iframe.getAttribute("id"),
          };

          slide.$iframe.contentWindow.postMessage(JSON.stringify(command), "*");
        }
      }

      slide.poller = setTimeout(poller, 250);
    };

    poller();
  }

  /**
   * Process `Carousel.onUnselectSlide` event to pause video
   * @param {Object} fancybox
   * @param {Object} carousel
   * @param {Object} slide
   */
  onUnselectSlide(fancybox, carousel, slide) {
    if (slide.type === "html5video") {
      try {
        slide.$el.querySelector("video").pause();
      } catch (error) {}

      return;
    }

    let command = false;

    if (slide.vendor == "vimeo") {
      command = {
        method: "pause",
        value: "true",
      };
    } else if (slide.vendor === "youtube") {
      command = {
        event: "command",
        func: "pauseVideo",
      };
    }

    if (command && slide.$iframe && slide.$iframe.contentWindow) {
      slide.$iframe.contentWindow.postMessage(JSON.stringify(command), "*");
    }

    clearTimeout(slide.poller);
  }

  /**
   * Process `Carousel.onRemoveSlide` event to do clean up
   * @param {Object} fancybox
   * @param {Object} carousel
   * @param {Object} slide
   */
  onRemoveSlide(fancybox, carousel, slide) {
    // Abort ajax request if exists
    if (slide.xhr) {
      slide.xhr.abort();
      slide.xhr = null;
    }

    // Unload iframe content if exists
    if (slide.$iframe) {
      slide.$iframe.onload = slide.$iframe.onerror = null;

      slide.$iframe.src = "//about:blank";
      slide.$iframe = null;
    }

    // Clear inline content
    const $content = slide.$content;

    if (slide.type === "inline" && $content) {
      $content.classList.remove("fancybox__content");

      if ($content.style.display !== "none") {
        $content.style.display = "none";
      }
    }

    if (slide.$closeButton) {
      slide.$closeButton.remove();
      slide.$closeButton = null;
    }

    const $placeHolder = $content && $content.$placeHolder;

    if ($placeHolder) {
      $placeHolder.parentNode.insertBefore($content, $placeHolder);
      $placeHolder.remove();
      $content.$placeHolder = null;
    }
  }

  /**
   * Process `window.message` event to mark video iframe element as `ready`
   * @param {Object} e - Event
   */
  onMessage(e) {
    try {
      let data = JSON.parse(e.data);

      if (e.origin === "https://player.vimeo.com") {
        if (data.event === "ready") {
          for (let $iframe of document.getElementsByClassName("fancybox__iframe")) {
            if ($iframe.contentWindow === e.source) {
              $iframe.isReady = 1;
            }
          }
        }
      } else if (e.origin === "https://www.youtube-nocookie.com") {
        if (data.event === "onReady") {
          document.getElementById(data.id).isReady = 1;
        }
      }
    } catch (ex) {}
  }

  attach() {
    this.fancybox.on(this.events);

    window.addEventListener("message", this.onMessage, false);
  }

  detach() {
    this.fancybox.off(this.events);

    window.removeEventListener("message", this.onMessage, false);
  }
}

// Expose defaults
Html.defaults = defaults;
