/* eslint-disable func-names, wrap-iife, no-use-before-define,
consistent-return, prefer-rest-params */
/* global Breakpoints */

import { bytesToKiB } from './lib/utils/number_utils';

const bind = function (fn, me) { return function () { return fn.apply(me, arguments); }; };
const AUTO_SCROLL_OFFSET = 75;
const DOWN_BUILD_TRACE = '#down-build-trace';

window.Build = (function () {
  Build.timeout = null;

  Build.state = null;

  function Build(options) {
    this.options = options || $('.js-build-options').data();

    this.pageUrl = this.options.pageUrl;
    this.buildUrl = this.options.buildUrl;
    this.buildStatus = this.options.buildStatus;
    this.state = this.options.logState;
    this.buildStage = this.options.buildStage;
    this.$document = $(document);
    this.logBytes = 0;

    this.updateDropdown = bind(this.updateDropdown, this);

    this.$body = $('body');
    this.$buildTrace = $('#build-trace');
    this.$autoScrollContainer = $('.autoscroll-container');
    this.$autoScrollStatus = $('#autoscroll-status');
    this.$autoScrollStatusText = this.$autoScrollStatus.find('.status-text');
    this.$upBuildTrace = $('#up-build-trace');
    this.$downBuildTrace = $(DOWN_BUILD_TRACE);
    this.$scrollTopBtn = $('#scroll-top');
    this.$scrollBottomBtn = $('#scroll-bottom');
    this.$buildRefreshAnimation = $('.js-build-refresh');
    this.$buildScroll = $('#js-build-scroll');
    this.$truncatedInfo = $('.js-truncated-info');

    clearTimeout(Build.timeout);
    // Init breakpoint checker
    this.bp = Breakpoints.get();

    this.initSidebar();
    this.populateJobs(this.buildStage);
    this.updateStageDropdownText(this.buildStage);
    this.sidebarOnResize();

    this.$document
      .off('click', '.js-sidebar-build-toggle')
      .on('click', '.js-sidebar-build-toggle', this.sidebarOnClick.bind(this));

    this.$document
      .off('click', '.stage-item')
      .on('click', '.stage-item', this.updateDropdown);

    this.$document.on('scroll', this.initScrollMonitor.bind(this));

    $(window)
      .off('resize.build')
      .on('resize.build', this.sidebarOnResize.bind(this));

    $('a', this.$buildScroll)
      .off('click.stepTrace')
      .on('click.stepTrace', this.stepTrace);

    this.updateArtifactRemoveDate();
    this.initScrollButtonAffix();
    this.invokeBuildTrace();
  }

  Build.prototype.initSidebar = function () {
    this.$sidebar = $('.js-build-sidebar');
    this.$sidebar.niceScroll();
    this.$document
      .off('click', '.js-sidebar-build-toggle')
      .on('click', '.js-sidebar-build-toggle', this.toggleSidebar);
  };

  Build.prototype.invokeBuildTrace = function () {
    return this.getBuildTrace();
  };

  Build.prototype.getBuildTrace = function () {
    return $.ajax({
      url: `${this.pageUrl}/trace.json`,
      dataType: 'json',
      data: {
        state: this.state,
      },
      success: ((log) => {
        const $buildContainer = $('.js-build-output');

        gl.utils.setCiStatusFavicon(`${this.pageUrl}/status.json`);

        if (log.state) {
          this.state = log.state;
        }

        if (log.append) {
          $buildContainer.append(log.html);
          this.logBytes += log.size;
        } else {
          $buildContainer.html(log.html);
          this.logBytes = log.size;
        }

        // if the incremental sum of logBytes we received is less than the total
        // we need to show a message warning the user about that.
        if (this.logBytes < log.total) {
          // size is in bytes, we need to calculate KiB
          const size = bytesToKiB(this.logBytes);
          $('.js-truncated-info-size').html(`${size}`);
          this.$truncatedInfo.removeClass('hidden');
          this.initAffixTruncatedInfo();
        } else {
          this.$truncatedInfo.addClass('hidden');
        }

        this.checkAutoscroll();

        if (!log.complete) {
          Build.timeout = setTimeout(() => {
            this.invokeBuildTrace();
          }, 4000);
        } else {
          this.$buildRefreshAnimation.remove();
        }

        if (log.status !== this.buildStatus) {
          let pageUrl = this.pageUrl;

          if (this.$autoScrollStatus.data('state') === 'enabled') {
            pageUrl += DOWN_BUILD_TRACE;
          }

          gl.utils.visitUrl(pageUrl);
        }
      }),
      error: () => {
        this.$buildRefreshAnimation.remove();
        return this.initScrollMonitor();
      },
    });
  };

  Build.prototype.checkAutoscroll = function () {
    if (this.$autoScrollStatus.data('state') === 'enabled') {
      return $('html,body').scrollTop(this.$buildTrace.height());
    }

    // Handle a situation where user started new build
    // but never scrolled a page
    if (!this.$scrollTopBtn.is(':visible') &&
        !this.$scrollBottomBtn.is(':visible') &&
        !gl.utils.isInViewport(this.$downBuildTrace.get(0))) {
      this.$scrollBottomBtn.show();
    }
  };

  Build.prototype.initScrollButtonAffix = function () {
    // Hide everything initially
    this.$scrollTopBtn.hide();
    this.$scrollBottomBtn.hide();
    this.$autoScrollContainer.hide();
  };

  // Page scroll listener to detect if user has scrolling page
  // and handle following cases
  // 1) User is at Top of Build Log;
  //      - Hide Top Arrow button
  //      - Show Bottom Arrow button
  //      - Disable Autoscroll and hide indicator (when build is running)
  // 2) User is at Bottom of Build Log;
  //      - Show Top Arrow button
  //      - Hide Bottom Arrow button
  //      - Enable Autoscroll and show indicator (when build is running)
  // 3) User is somewhere in middle of Build Log;
  //      - Show Top Arrow button
  //      - Show Bottom Arrow button
  //      - Disable Autoscroll and hide indicator (when build is running)
  Build.prototype.initScrollMonitor = function () {
    if (!gl.utils.isInViewport(this.$upBuildTrace.get(0)) &&
      !gl.utils.isInViewport(this.$downBuildTrace.get(0))) {
      // User is somewhere in middle of Build Log

      this.$scrollTopBtn.show();

      if (this.buildStatus === 'success' || this.buildStatus === 'failed') { // Check if Build is completed
        this.$scrollBottomBtn.show();
      } else if (this.$buildRefreshAnimation.is(':visible') &&
        !gl.utils.isInViewport(this.$buildRefreshAnimation.get(0))) {
        this.$scrollBottomBtn.show();
      } else {
        this.$scrollBottomBtn.hide();
      }

      // Hide Autoscroll Status Indicator
      if (this.$scrollBottomBtn.is(':visible')) {
        this.$autoScrollContainer.hide();
        this.$autoScrollStatusText.removeClass('animate');
      } else {
        this.$autoScrollContainer.css({
          top: this.$body.outerHeight() - AUTO_SCROLL_OFFSET,
        }).show();
        this.$autoScrollStatusText.addClass('animate');
      }
    } else if (gl.utils.isInViewport(this.$upBuildTrace.get(0)) &&
      !gl.utils.isInViewport(this.$downBuildTrace.get(0))) {
      // User is at Top of Build Log

      this.$scrollTopBtn.hide();
      this.$scrollBottomBtn.show();

      this.$autoScrollContainer.hide();
      this.$autoScrollStatusText.removeClass('animate');
    } else if ((!gl.utils.isInViewport(this.$upBuildTrace.get(0)) &&
      gl.utils.isInViewport(this.$downBuildTrace.get(0))) ||
      (this.$buildRefreshAnimation.is(':visible') &&
      gl.utils.isInViewport(this.$buildRefreshAnimation.get(0)))) {
      // User is at Bottom of Build Log

      this.$scrollTopBtn.show();
      this.$scrollBottomBtn.hide();

      // Show and Reposition Autoscroll Status Indicator
      this.$autoScrollContainer.css({
        top: this.$body.outerHeight() - AUTO_SCROLL_OFFSET,
      }).show();
      this.$autoScrollStatusText.addClass('animate');
    } else if (gl.utils.isInViewport(this.$upBuildTrace.get(0)) &&
      gl.utils.isInViewport(this.$downBuildTrace.get(0))) {
      // Build Log height is small

      this.$scrollTopBtn.hide();
      this.$scrollBottomBtn.hide();

      // Hide Autoscroll Status Indicator
      this.$autoScrollContainer.hide();
      this.$autoScrollStatusText.removeClass('animate');
    }

    if (this.buildStatus === 'running' || this.buildStatus === 'pending') {
      // Check if Refresh Animation is in Viewport and enable Autoscroll, disable otherwise.
      this.$autoScrollStatus.data(
        'state',
        gl.utils.isInViewport(this.$buildRefreshAnimation.get(0)) ? 'enabled' : 'disabled',
      );
    }
  };

  Build.prototype.shouldHideSidebarForViewport = function () {
    const bootstrapBreakpoint = this.bp.getBreakpointSize();
    return bootstrapBreakpoint === 'xs' || bootstrapBreakpoint === 'sm';
  };

  Build.prototype.toggleSidebar = function (shouldHide) {
    const shouldShow = typeof shouldHide === 'boolean' ? !shouldHide : undefined;

    this.$buildScroll.toggleClass('sidebar-expanded', shouldShow)
      .toggleClass('sidebar-collapsed', shouldHide);
    this.$truncatedInfo.toggleClass('sidebar-expanded', shouldShow)
      .toggleClass('sidebar-collapsed', shouldHide);
    this.$sidebar.toggleClass('right-sidebar-expanded', shouldShow)
      .toggleClass('right-sidebar-collapsed', shouldHide);
  };

  Build.prototype.sidebarOnResize = function () {
    this.toggleSidebar(this.shouldHideSidebarForViewport());
  };

  Build.prototype.sidebarOnClick = function () {
    if (this.shouldHideSidebarForViewport()) this.toggleSidebar();
  };

  Build.prototype.updateArtifactRemoveDate = function () {
    const $date = $('.js-artifacts-remove');
    if ($date.length) {
      const date = $date.text();
      return $date.text(
        gl.utils.timeFor(new Date(date.replace(/([0-9]+)-([0-9]+)-([0-9]+)/g, '$1/$2/$3')), ' '),
      );
    }
  };

  Build.prototype.populateJobs = function (stage) {
    $('.build-job').hide();
    $(`.build-job[data-stage="${stage}"]`).show();
  };

  Build.prototype.updateStageDropdownText = function (stage) {
    $('.stage-selection').text(stage);
  };

  Build.prototype.updateDropdown = function (e) {
    e.preventDefault();
    const stage = e.currentTarget.text;
    this.updateStageDropdownText(stage);
    this.populateJobs(stage);
  };

  Build.prototype.stepTrace = function (e) {
    e.preventDefault();

    const $currentTarget = $(e.currentTarget);
    $.scrollTo($currentTarget.attr('href'), {
      offset: 0,
    });
  };

  Build.prototype.initAffixTruncatedInfo = function () {
    const offsetTop = this.$buildTrace.offset().top;

    this.$truncatedInfo.affix({
      offset: {
        top: offsetTop,
      },
    });
  };

  return Build;
})();
