/* NProgress, (c) 2013, 2014 Rico Sta. Cruz - http://ricostacruz.com/nprogress
 * @license MIT */
// 模块化封装（兼容 AMD/CommonJS/全局变量）
;(function(root, factory) {

  if (typeof define === 'function' && define.amd) {
    define(factory); // AMD 模块规范（如 RequireJS）
  } else if (typeof exports === 'object') {
    module.exports = factory(); // CommonJS 模块规范（如 Node.js）
  } else {
    root.NProgress = factory(); // 全局变量（浏览器环境）
  }

})(this, function() {
  var NProgress = {};

  NProgress.version = '0.2.0';
// 默认配置项（可通过 configure 方法修改）
  var Settings = NProgress.settings = {
    minimum: 0.08,
    easing: 'linear',
    positionUsing: '',
    speed: 200,
    trickle: true,
    trickleSpeed: 200,
    showSpinner: true,
    barSelector: '[role="bar"]',
    spinnerSelector: '[role="spinner"]',
    parent: 'body',
    template: '<div class="bar" role="bar"><div class="peg"></div></div><div class="spinner" role="spinner"><div class="spinner-icon"></div></div>'
  };

  /**
   * Updates configuration.
   *
   *     NProgress.configure({
   *       minimum: 0.1
   *     });
   */
   /**
   * 修改配置项
   * @param {Object} options - 要修改的配置（键值对）
   * @return {Object} NProgress 对象（支持链式调用）
   */
  NProgress.configure = function(options) {
    var key, value;
    for (key in options) {
      value = options[key];
      if (value !== undefined && options.hasOwnProperty(key)) Settings[key] = value;
    }

    return this;
  };

  /**
   * Last number.
   */
    /**
   * 设置进度条进度
   * @param {number} n - 进度值（0.0-1.0）
   * @return {Object} NProgress 对象（支持链式调用）
   */
  NProgress.status = null;

  /**
   * Sets the progress bar status, where `n` is a number from `0.0` to `1.0`.
   *
   *     NProgress.set(0.4);
   *     NProgress.set(1.0);
   */

  NProgress.set = function(n) {
    var started = NProgress.isStarted();

    n = clamp(n, Settings.minimum, 1);
    NProgress.status = (n === 1 ? null : n);

    var progress = NProgress.render(!started),
        bar      = progress.querySelector(Settings.barSelector),
        speed    = Settings.speed,
        ease     = Settings.easing;

    progress.offsetWidth; /* Repaint */
    // 队列执行进度更新（避免并发动画冲突）
    queue(function(next) {
      // Set positionUsing if it hasn't already been set
      if (Settings.positionUsing === '') Settings.positionUsing = NProgress.getPositioningCSS();

      // Add transition
      // 应用进度条位置动画
      css(bar, barPositionCSS(n, speed, ease));

      if (n === 1) {
        // Fade out
        // 进度完成：先显示满进度，再渐隐
        css(progress, {
          transition: 'none',
          opacity: 1
        });
        progress.offsetWidth; /* Repaint */

        setTimeout(function() {
          css(progress, {
            transition: 'all ' + speed + 'ms linear',
            opacity: 0
          });
          // 动画结束后移除进度条
          setTimeout(function() {
            NProgress.remove();
            next();
          }, speed);
        }, speed);
      } else {
        setTimeout(next, speed);
      }
    });

    return this;
  };
  /**
   * 判断进度条是否已启动
   * @return {boolean} 已启动返回 true，否则 false
   */
  NProgress.isStarted = function() {
    return typeof NProgress.status === 'number';
  };

  /**
   * Shows the progress bar.
   * This is the same as setting the status to 0%, except that it doesn't go backwards.
   *
   *     NProgress.start();
   *
   */
   /**
   * 启动进度条（从最小进度开始自动递增）
   * @return {Object} NProgress 对象（支持链式调用）
   */
  NProgress.start = function() {
    if (!NProgress.status) NProgress.set(0);

    var work = function() {
      setTimeout(function() {
        if (!NProgress.status) return;
        NProgress.trickle();
        work();
      }, Settings.trickleSpeed);
    };
    // 开启自动递增（根据配置）
    if (Settings.trickle) work();

    return this;
  };

  /**
   * Hides the progress bar.
   * This is the *sort of* the same as setting the status to 100%, with the
   * difference being `done()` makes some placebo effect of some realistic motion.
   *
   *     NProgress.done();
   *
   * If `true` is passed, it will show the progress bar even if its hidden.
   *
   *     NProgress.done(true);
   */
  /**
   * 结束进度条（平滑过渡到 100% 后隐藏）
   * @param {boolean} force - 强制显示（即使未启动）
   * @return {Object} NProgress 对象（支持链式调用）
   */
  NProgress.done = function(force) {
    if (!force && !NProgress.status) return this;

    return NProgress.inc(0.3 + 0.5 * Math.random()).set(1);
  };

  /**
   * Increments by a random amount.
   */
  /**
   * 递增进度（可指定增量，未指定则按规则自动递增）
   * @param {number} [amount] - 增量（0-1）
   * @return {Object} NProgress 对象（支持链式调用）
   */
  NProgress.inc = function(amount) {
    var n = NProgress.status;

    if (!n) {
      return NProgress.start();
    } else if(n > 1) {

    } else {
      if (typeof amount !== 'number') {
        if (n >= 0 && n < 0.2) { amount = 0.1; }
        else if (n >= 0.2 && n < 0.5) { amount = 0.04; }
        else if (n >= 0.5 && n < 0.8) { amount = 0.02; }
        else if (n >= 0.8 && n < 0.99) { amount = 0.005; }
        else { amount = 0; }
      }

      n = clamp(n + amount, 0, 0.994);
      return NProgress.set(n);
    }
  };
  /**
   * 自动递增进度（调用 inc 无参数版本）
   * @return {Object} NProgress 对象（支持链式调用）
   */
  NProgress.trickle = function() {
    return NProgress.inc();
  };

  /**
   * Waits for all supplied jQuery promises and
   * increases the progress as the promises resolve.
   *
   * @param $promise jQUery Promise
   */
   /**
   * 监听 jQuery Promise 进度（多个 Promise 完成时自动更新进度）
   * @param {Object} $promise - jQuery Promise 对象
   * @return {Object} NProgress 对象（支持链式调用）
   */
  (function() {
    var initial = 0, current = 0;

    NProgress.promise = function($promise) {
      if (!$promise || $promise.state() === "resolved") {
        return this;
      }

      if (current === 0) {
        NProgress.start();
      }

      initial++;
      current++;
      // Promise 完成（成功/失败）时更新进度
      $promise.always(function() {
        current--;
        if (current === 0) {
            initial = 0;
            NProgress.done();
        } else {
            NProgress.set((initial - current) / initial);
        }
      });

      return this;
    };

  })();

  /**
   * (Internal) renders the progress bar markup based on the `template`
   * setting.
   */
  /**
   * 渲染进度条（首次调用时生成 DOM 并插入页面）
   * @param {boolean} fromStart - 是否从初始状态（-100%）开始
   * @return {HTMLElement} 进度条 DOM 元素
   */
  NProgress.render = function(fromStart) {
    if (NProgress.isRendered()) return document.getElementById('nprogress');

    addClass(document.documentElement, 'nprogress-busy');
    // 创建进度条容器
    var progress = document.createElement('div');
    progress.id = 'nprogress';
    progress.innerHTML = Settings.template;

    var bar      = progress.querySelector(Settings.barSelector),
        perc     = fromStart ? '-100' : toBarPerc(NProgress.status || 0),
        parent   = document.querySelector(Settings.parent),
        spinner;
    // 初始进度条位置（无动画）
    css(bar, {
      transition: 'all 0 linear',
      transform: 'translate3d(' + perc + '%,0,0)'
    });
    // 不显示 spinner 时，移除 spinner 元素
    if (!Settings.showSpinner) {
      spinner = progress.querySelector(Settings.spinnerSelector);
      spinner && removeElement(spinner);
    }
    // 父容器非 body 时，添加自定义父容器类（用于样式适配）
    if (parent != document.body) {
      addClass(parent, 'nprogress-custom-parent');
    }

    parent.appendChild(progress);
    return progress;
  };

  /**
   * Removes the element. Opposite of render().
   */
  /**
   * 移除进度条（从 DOM 中删除）
   */
  NProgress.remove = function() {
    removeClass(document.documentElement, 'nprogress-busy');
    removeClass(document.querySelector(Settings.parent), 'nprogress-custom-parent');
    var progress = document.getElementById('nprogress');
    progress && removeElement(progress);
  };

  /**
   * Checks if the progress bar is rendered.
   */
  /**
   * 判断进度条是否已渲染
   * @return {boolean} 已渲染返回 true，否则 false
   */
  NProgress.isRendered = function() {
    return !!document.getElementById('nprogress');
  };

  /**
   * Determine which positioning CSS rule to use.
   */
  /**
   * 自动检测浏览器支持的位置计算方式（优先 3D 变换，提升性能）
   * @return {string} 位置计算方式（translate3d/translate/margin）
   */
  NProgress.getPositioningCSS = function() {
    // Sniff on document.body.style
    var bodyStyle = document.body.style;

    // Sniff prefixes
    var vendorPrefix = ('WebkitTransform' in bodyStyle) ? 'Webkit' :
                       ('MozTransform' in bodyStyle) ? 'Moz' :
                       ('msTransform' in bodyStyle) ? 'ms' :
                       ('OTransform' in bodyStyle) ? 'O' : '';

    if (vendorPrefix + 'Perspective' in bodyStyle) {
      // Modern browsers with 3D support, e.g. Webkit, IE10
      return 'translate3d';
    } else if (vendorPrefix + 'Transform' in bodyStyle) {
      // Browsers without 3D support, e.g. IE9
      return 'translate';
    } else {
      // Browsers without translate() support, e.g. IE7-8
      return 'margin';
    }
  };

  /**
   * Helpers
   */
  /**
   * 工具函数：限制数值在 min 和 max 之间
   * @param {number} n - 目标数值
   * @param {number} min - 最小值
   * @param {number} max - 最大值
   * @return {number} 限制后的值
   */
  function clamp(n, min, max) {
    if (n < min) return min;
    if (n > max) return max;
    return n;
  }

  /**
   * (Internal) converts a percentage (`0..1`) to a bar translateX
   * percentage (`-100%..0%`).
   */
  /**
   * 工具函数：将进度（0-1）转为进度条位移比例（-100% 到 0%）
   * @param {number} n - 进度值（0-1）
   * @return {number} 位移比例（-100 到 0）
   */
  function toBarPerc(n) {
    return (-1 + n) * 100;
  }


  /**
   * (Internal) returns the correct CSS for changing the bar's
   * position given an n percentage, and speed and ease from Settings
   */
  /**
   * 工具函数：生成进度条位置动画的 CSS 样式
   * @param {number} n - 进度值（0-1）
   * @param {number} speed - 动画速度（毫秒）
   * @param {string} ease - 缓动函数
   * @return {Object} CSS 样式对象
   */
  function barPositionCSS(n, speed, ease) {
    var barCSS;
    // 根据位置计算方式生成对应 CSS
    if (Settings.positionUsing === 'translate3d') {
      barCSS = { transform: 'translate3d('+toBarPerc(n)+'%,0,0)' };
    } else if (Settings.positionUsing === 'translate') {
      barCSS = { transform: 'translate('+toBarPerc(n)+'%,0)' };
    } else {
      barCSS = { 'margin-left': toBarPerc(n)+'%' };
    }

    barCSS.transition = 'all '+speed+'ms '+ease;

    return barCSS;
  }

  /**
   * (Internal) Queues a function to be executed.
   */
  /**
   * 工具函数：任务队列（确保动画按顺序执行，避免并发冲突）
   * @return {Function} 入队函数
   */
  var queue = (function() {
    var pending = [];  // 待执行任务队列

    function next() {
      var fn = pending.shift();
      if (fn) {
        fn(next);  // 任务执行完后调用 next，触发下一个任务
      }
    }

    return function(fn) {
      pending.push(fn);
      if (pending.length == 1) next();
    };
  })();

  /**
   * (Internal) Applies css properties to an element, similar to the jQuery
   * css method.
   *
   * While this helper does assist with vendor prefixed property names, it
   * does not perform any manipulation of values prior to setting styles.
   */
  /**
   * 工具函数：给元素设置 CSS 样式（兼容浏览器前缀）
   * @param {HTMLElement} element - 目标元素
   * @param {Object/string} properties - 样式对象或属性名
   * @param {string} [value] - 样式值（properties 为属性名时必填）
   */
  var css = (function() {
    var cssPrefixes = [ 'Webkit', 'O', 'Moz', 'ms' ],
        cssProps    = {};
    // 转换为驼峰命名（如 margin-left → marginLeft
    function camelCase(string) {
      return string.replace(/^-ms-/, 'ms-').replace(/-([\da-z])/gi, function(match, letter) {
        return letter.toUpperCase();
      });
    }
    // 检测属性的浏览器前缀版本
    function getVendorProp(name) {
      var style = document.body.style;
      if (name in style) return name;

      var i = cssPrefixes.length,
          capName = name.charAt(0).toUpperCase() + name.slice(1),
          vendorName;
      while (i--) {
        vendorName = cssPrefixes[i] + capName;
        if (vendorName in style) return vendorName;
      }

      return name;
    }
    // 获取最终的样式属性名（缓存结果）
    function getStyleProp(name) {
      name = camelCase(name);
      return cssProps[name] || (cssProps[name] = getVendorProp(name));
    }

    function applyCss(element, prop, value) {
      prop = getStyleProp(prop);
      element.style[prop] = value;
    }
    // 入口函数（支持对象批量设置或单个设置）
    return function(element, properties) {
      var args = arguments,
          prop,
          value;

      if (args.length == 2) {
        for (prop in properties) {
          value = properties[prop];
          if (value !== undefined && properties.hasOwnProperty(prop)) applyCss(element, prop, value);
        }
      } else {
        applyCss(element, args[1], args[2]);
      }
    }
  })();

  /**
   * (Internal) Determines if an element or space separated list of class names contains a class name.
   */
  /**
   * 工具函数：判断元素是否包含指定类名
   * @param {HTMLElement/string} element - 目标元素或类名列表
   * @param {string} name - 要判断的类名
   * @return {boolean} 包含返回 true，否则 false
   */
  function hasClass(element, name) {
    var list = typeof element == 'string' ? element : classList(element);
    return list.indexOf(' ' + name + ' ') >= 0;
  }

  /**
   * (Internal) Adds a class to an element.
   */
  /**
   * 工具函数：给元素移除类名
   * @param {HTMLElement} element - 目标元素
   * @param {string} name - 要移除的类名
   */
  function addClass(element, name) {
    var oldList = classList(element),
        newList = oldList + name;

    if (hasClass(oldList, name)) return;

    // Trim the opening space.
    element.className = newList.substring(1);
  }

  /**
   * (Internal) Removes a class from an element.
   */

  function removeClass(element, name) {
    var oldList = classList(element),
        newList;

    if (!hasClass(element, name)) return;

    // Replace the class name.
    // 替换类名（用空格包裹，避免部分匹配）
    newList = oldList.replace(' ' + name + ' ', ' ');

    // Trim the opening and closing spaces.
    element.className = newList.substring(1, newList.length - 1);
  }

  /**
   * (Internal) Gets a space separated list of the class names on the element.
   * The list is wrapped with a single space on each end to facilitate finding
   * matches within the list.
   */

  function classList(element) {
    return (' ' + (element && element.className || '') + ' ').replace(/\s+/gi, ' ');
  }

  /**
   * (Internal) Removes an element from the DOM.
   */
  /**
   * 工具函数：从 DOM 中移除元素
   * @param {HTMLElement} element - 要移除的元素
   */
  function removeElement(element) {
    element && element.parentNode && element.parentNode.removeChild(element);
  }

  return NProgress;
});
