"use strict";
const conversions = require("webidl-conversions");
const DOMException = require("domexception");
const FileList = require("../generated/FileList");
const HTMLElementImpl = require("./HTMLElement-impl").implementation;
const idlUtils = require("../generated/utils");
const DefaultConstraintValidationImpl =
  require("../constraint-validation/DefaultConstraintValidation-impl").implementation;
const ValidityState = require("../generated/ValidityState");
const { mixin } = require("../../utils");
const { domSymbolTree, cloningSteps } = require("../helpers/internal-constants");
const { getLabelsForLabelable, formOwner } = require("../helpers/form-controls");
const { fireAnEvent } = require("../helpers/events");
const {
  isDisabled,
  isValidEmailAddress,
  isValidAbsoluteURL,
  sanitizeValueByType
} = require("../helpers/form-controls");
const {
  parseFloatingPointNumber,
  asciiCaseInsensitiveMatch,
  splitOnCommas
} = require("../helpers/strings");
const {
  parseDateString,
  parseLocalDateAndTimeString,
  parseMonthString,
  parseTimeString,
  parseWeekString
} = require("../helpers/dates-and-times");

const filesSymbol = Symbol("files");

const selectAllowedTypes = new Set([
  "text", "search", "tel", "url", "password", "email", "date", "month", "week",
  "time", "datetime-local", "color", "file", "number"
]);

const variableLengthSelectionAllowedTypes = new Set(["text", "search", "tel", "url", "password"]);

const maxMinStepTypes = new Set(["date", "month", "week", "time", "datetime-local", "number", "range", "datetime"]);

// https://html.spec.whatwg.org/multipage/input.html#concept-input-apply
const applicableTypesForAttribute = {
  max: maxMinStepTypes,
  min: maxMinStepTypes,
  step: maxMinStepTypes,
  pattern: new Set(["text", "search", "tel", "url", "email", "password"])
};

function allowSelect(type) {
  return selectAllowedTypes.has(type.toLowerCase());
}

function allowVariableLengthSelection(type) {
  return variableLengthSelectionAllowedTypes.has(type.toLowerCase());
}

const valueAttributeDefaultMode = new Set(["hidden", "submit", "image", "reset", "button"]);
const valueAttributeDefaultOnMode = new Set(["checkbox", "radio"]);

function valueAttributeMode(type) {
  if (valueAttributeDefaultMode.has(type)) {
    return "default";
  }
  if (valueAttributeDefaultOnMode.has(type)) {
    return "default/on";
  }
  if (type === "file") {
    return "filename";
  }
  return "value";
}

// Necessary because Date.UTC() treats year within [0, 99] as [1900, 1999].
function getUTCMs(year, month = 1, day = 1, hour = 0, minute = 0, second = 0, millisecond = 0) {
  if (year > 99 || year < 0) {
    return Date.UTC(year, month - 1, day, hour, minute, second, millisecond);
  }
  const d = new Date(0);
  d.setUTCFullYear(year);
  d.setUTCMonth(month - 1);
  d.setUTCDate(day);
  d.setUTCHours(hour);
  d.setUTCMinutes(minute);
  d.setUTCSeconds(second, millisecond);
  return d.valueOf();
}

const dayOfWeekRelMondayLUT = [-1, 0, 1, 2, 3, -3, -2];

const convertStringToNumberByTypeMap = new Map([
  [
    // https://html.spec.whatwg.org/multipage/input.html#date-state-(type=date):concept-input-value-string-number
    "date", input => {
      const date = parseDateString(input);
      if (date === null) {
        return NaN;
      }
      return getUTCMs(date.year, date.month, date.day);
    }
  ],
  [
    // https://html.spec.whatwg.org/multipage/input.html#month-state-(type=month):concept-input-value-string-number
    "month", input => {
      const date = parseMonthString(input);
      if (date === null) {
        return NaN;
      }
      return (date.year - 1970) * 12 + (date.month - 1);
    }
  ],
  [
    // https://html.spec.whatwg.org/multipage/input.html#week-state-(type=week):concept-input-value-string-number
    "week", input => {
      const date = parseWeekString(input);
      if (date === null) {
        return NaN;
      }
      const dateObj = new Date(getUTCMs(date.year));
      // An HTML week starts on Monday, while 0 represents Sunday. Account for such.
      const dayOfWeekRelMonday = dayOfWeekRelMondayLUT[dateObj.getUTCDay()];
      return dateObj.setUTCDate(1 + 7 * (date.week - 1) - dayOfWeekRelMonday);
    }
  ],
  [
    // https://html.spec.whatwg.org/multipage/input.html#time-state-(type=time):concept-input-value-string-number
    "time", input => {
      const time = parseTimeString(input);
      if (time === null) {
        return NaN;
      }
      return ((time.hour * 60 + time.minute) * 60 + time.second) * 1000 + time.millisecond;
    }
  ],
  [
    // https://html.spec.whatwg.org/multipage/input.html#local-date-and-time-state-(type=datetime-local):concept-input-value-string-number
    "datetime-local", input => {
      const dateAndTime = parseLocalDateAndTimeString(input);
      if (dateAndTime === null) {
        return NaN;
      }
      const { date: { year, month, day }, time: { hour, minute, second, millisecond } } = dateAndTime;
      // Doesn't quite matter whether or not UTC is used, since the offset from 1970-01-01 local time is returned.
      return getUTCMs(year, month, day, hour, minute, second, millisecond);
    }
  ],
  // https://html.spec.whatwg.org/multipage/input.html#number-state-(type=number):concept-input-value-string-number
  ["number", parseFloatingPointNumber],
  // https://html.spec.whatwg.org/multipage/input.html#range-state-(type=range):concept-input-value-string-number
  ["range", parseFloatingPointNumber]
]);

class HTMLInputElementImpl extends HTMLElementImpl {
  constructor(args, privateData) {
    super(args, privateData);

    this._selectionStart = this._selectionEnd = 0;
    this._selectionDirection = "none";
    this._value = null;
    this._dirtyValue = false;
    this._checkedness = false;
    this._dirtyCheckedness = false;

    this._preCheckedRadioState = null;

    this.indeterminate = false;

    this._customValidityErrorMessage = "";

    this._labels = null;

    this._hasActivationBehavior = true;
  }

  // https://html.spec.whatwg.org/multipage/input.html#concept-input-value-string-number
  get _convertStringToNumber() {
    return convertStringToNumberByTypeMap.get(this.type);
  }

  _getValue() {
    return this._value;
  }

  _legacyPreActivationBehavior() {
    if (this.type === "checkbox") {
      this.checked = !this.checked;
    } else if (this.type === "radio") {
      this._preCheckedRadioState = this.checked;
      this.checked = true;
    }
  }

  _legacyCanceledActivationBehavior() {
    if (this.type === "checkbox") {
      this.checked = !this.checked;
    } else if (this.type === "radio") {
      if (this._preCheckedRadioState !== null) {
        this.checked = this._preCheckedRadioState;
        this._preCheckedRadioState = null;
      }
    }
  }

  _activationBehavior() {
    if (isDisabled(this)) {
      return;
    }

    const { form } = this;

    if (this.type === "checkbox" || (this.type === "radio" && !this._preCheckedRadioState)) {
      fireAnEvent("input", this, undefined, { bubbles: true });
      fireAnEvent("change", this, undefined, { bubbles: true });
    } else if (form && this.type === "submit") {
      form._doSubmit();
    } else if (form && this.type === "reset") {
      form._doReset();
    }
  }

  _attrModified(name) {
    const wrapper = idlUtils.wrapperForImpl(this);
    if (!this._dirtyValue && name === "value") {
      this._value = sanitizeValueByType(this, wrapper.defaultValue);
    }
    if (!this._dirtyCheckedness && name === "checked") {
      this._checkedness = wrapper.defaultChecked;
      if (this._checkedness) {
        this._removeOtherRadioCheckedness();
      }
    }

    if (name === "name" || name === "type") {
      if (this._checkedness) {
        this._removeOtherRadioCheckedness();
      }
    }

    super._attrModified.apply(this, arguments);
  }

  _formReset() {
    const wrapper = idlUtils.wrapperForImpl(this);
    this._value = sanitizeValueByType(this, wrapper.defaultValue);
    this._dirtyValue = false;
    this._checkedness = wrapper.defaultChecked;
    this._dirtyCheckedness = false;
    if (this._checkedness) {
      this._removeOtherRadioCheckedness();
    }
  }

  _changedFormOwner() {
    if (this._checkedness) {
      this._removeOtherRadioCheckedness();
    }
  }

  get _otherRadioGroupElements() {
    const wrapper = idlUtils.wrapperForImpl(this);
    const root = this._radioButtonGroupRoot;
    if (!root) {
      return [];
    }

    const result = [];

    const descendants = domSymbolTree.treeIterator(root);
    for (const candidate of descendants) {
      if (candidate._radioButtonGroupRoot !== root) {
        continue;
      }

      const candidateWrapper = idlUtils.wrapperForImpl(candidate);
      if (!candidateWrapper.name || candidateWrapper.name !== wrapper.name) {
        continue;
      }

      if (candidate !== this) {
        result.push(candidate);
      }
    }
    return result;
  }

  _removeOtherRadioCheckedness() {
    for (const radioGroupElement of this._otherRadioGroupElements) {
      radioGroupElement._checkedness = false;
    }
  }

  get _radioButtonGroupRoot() {
    const wrapper = idlUtils.wrapperForImpl(this);
    if (this.type !== "radio" || !wrapper.name) {
      return null;
    }

    let e = domSymbolTree.parent(this);
    while (e) {
      // root node of this home sub tree
      // or the form element we belong to
      if (!domSymbolTree.parent(e) || e.nodeName.toUpperCase() === "FORM") {
        return e;
      }
      e = domSymbolTree.parent(e);
    }
    return null;
  }

  _isRadioGroupChecked() {
    if (this.checked) {
      return true;
    }
    return this._otherRadioGroupElements.some(radioGroupElement => radioGroupElement.checked);
  }

  get labels() {
    return getLabelsForLabelable(this);
  }

  get form() {
    return formOwner(this);
  }

  get checked() {
    return this._checkedness;
  }

  set checked(checked) {
    this._checkedness = Boolean(checked);
    this._dirtyCheckedness = true;
    if (this._checkedness) {
      this._removeOtherRadioCheckedness();
    }
  }

  get value() {
    switch (valueAttributeMode(this.type)) {
      // https://html.spec.whatwg.org/multipage/input.html#dom-input-value-value
      case "value":
        return this._value !== null ? this._value : "";
      // https://html.spec.whatwg.org/multipage/input.html#dom-input-value-default
      case "default": {
        const attr = this.getAttributeNS(null, "value");
        return attr !== null ? attr : "";
      }
      // https://html.spec.whatwg.org/multipage/input.html#dom-input-value-default-on
      case "default/on": {
        const attr = this.getAttributeNS(null, "value");
        return attr !== null ? attr : "on";
      }
      // https://html.spec.whatwg.org/multipage/input.html#dom-input-value-filename
      case "filename":
        return this.files.length ? "C:\\fakepath\\" + this.files[0].name : "";
      default:
        throw new Error("jsdom internal error: unknown value attribute mode");
    }
  }

  set value(val) {
    switch (valueAttributeMode(this.type)) {
      // https://html.spec.whatwg.org/multipage/input.html#dom-input-value-value
      case "value": {
        const oldValue = this._value;
        if (val === null) {
          this._value = null;
        } else {
          this._value = sanitizeValueByType(this, String(val));
        }
        this._dirtyValue = true;

        if (oldValue !== this._value) {
          this._selectionStart = 0;
          this._selectionEnd = 0;
          this._selectionDirection = "none";
        }
        break;
      }

      // https://html.spec.whatwg.org/multipage/input.html#dom-input-value-default
      // https://html.spec.whatwg.org/multipage/input.html#dom-input-value-default-on
      case "default":
      case "default/on":
        this.setAttributeNS(null, "value", val);
        break;

      // https://html.spec.whatwg.org/multipage/input.html#dom-input-value-filename
      case "filename":
        if (val === "") {
          this.files.length = 0;
        } else {
          throw new DOMException("This input element accepts a filename, which may only be programmatically set to " +
            "the empty string.", "InvalidStateError");
        }
        break;

      default:
        throw new Error("jsdom internal error: unknown value attribute mode");
    }
  }

  get files() {
    if (this.type === "file") {
      this[filesSymbol] = this[filesSymbol] || FileList.createImpl();
    } else {
      this[filesSymbol] = null;
    }
    return this[filesSymbol];
  }

  set files(value) {
    if (this.type === "file" && value !== null) {
      this[filesSymbol] = value;
    }
  }

  get type() {
    const type = this.getAttributeNS(null, "type");
    return type ? type.toLowerCase() : "text";
  }

  set type(type) {
    this.setAttributeNS(null, "type", type);
  }

  _dispatchSelectEvent() {
    fireAnEvent("select", this, undefined, { bubbles: true, cancelable: true });
  }

  _getValueLength() {
    return typeof this.value === "string" ? this.value.length : 0;
  }

  select() {
    if (!allowSelect(this.type)) {
      return;
    }

    this._selectionStart = 0;
    this._selectionEnd = this._getValueLength();
    this._selectionDirection = "none";
    this._dispatchSelectEvent();
  }

  get selectionStart() {
    if (!allowVariableLengthSelection(this.type)) {
      return null;
    }

    return this._selectionStart;
  }

  set selectionStart(start) {
    if (!allowVariableLengthSelection(this.type)) {
      throw new DOMException("The object is in an invalid state.", "InvalidStateError");
    }

    this.setSelectionRange(start, Math.max(start, this._selectionEnd), this._selectionDirection);
  }

  get selectionEnd() {
    if (!allowVariableLengthSelection(this.type)) {
      return null;
    }

    return this._selectionEnd;
  }

  set selectionEnd(end) {
    if (!allowVariableLengthSelection(this.type)) {
      throw new DOMException("The object is in an invalid state.", "InvalidStateError");
    }

    this.setSelectionRange(this._selectionStart, end, this._selectionDirection);
  }

  get selectionDirection() {
    if (!allowVariableLengthSelection(this.type)) {
      return null;
    }

    return this._selectionDirection;
  }

  set selectionDirection(dir) {
    if (!allowVariableLengthSelection(this.type)) {
      throw new DOMException("The object is in an invalid state.", "InvalidStateError");
    }

    this.setSelectionRange(this._selectionStart, this._selectionEnd, dir);
  }

  setSelectionRange(start, end, dir) {
    if (!allowVariableLengthSelection(this.type)) {
      throw new DOMException("The object is in an invalid state.", "InvalidStateError");
    }

    this._selectionEnd = Math.min(end, this._getValueLength());
    this._selectionStart = Math.min(start, this._selectionEnd);
    this._selectionDirection = dir === "forward" || dir === "backward" ? dir : "none";
    this._dispatchSelectEvent();
  }

  setRangeText(repl, start, end, selectionMode = "preserve") {
    if (!allowVariableLengthSelection(this.type)) {
      throw new DOMException("The object is in an invalid state.", "InvalidStateError");
    }

    if (arguments.length < 2) {
      start = this._selectionStart;
      end = this._selectionEnd;
    } else if (start > end) {
      throw new DOMException("The index is not in the allowed range.", "IndexSizeError");
    }

    start = Math.min(start, this._getValueLength());
    end = Math.min(end, this._getValueLength());

    const val = this.value;
    let selStart = this._selectionStart;
    let selEnd = this._selectionEnd;

    this.value = val.slice(0, start) + repl + val.slice(end);

    const newEnd = start + this.value.length;

    if (selectionMode === "select") {
      this.setSelectionRange(start, newEnd);
    } else if (selectionMode === "start") {
      this.setSelectionRange(start, start);
    } else if (selectionMode === "end") {
      this.setSelectionRange(newEnd, newEnd);
    } else { // preserve
      const delta = repl.length - (end - start);

      if (selStart > end) {
        selStart += delta;
      } else if (selStart > start) {
        selStart = start;
      }

      if (selEnd > end) {
        selEnd += delta;
      } else if (selEnd > start) {
        selEnd = newEnd;
      }

      this.setSelectionRange(selStart, selEnd);
    }
  }

  // https://html.spec.whatwg.org/multipage/input.html#the-list-attribute
  get list() {
    const id = this.getAttributeNS(null, "list");
    const el = this.getRootNode({}).getElementById(id);

    if (el && el.localName === "datalist") {
      return el;
    }

    return null;
  }

  set maxLength(value) {
    if (value < 0) {
      throw new DOMException("The index is not in the allowed range.", "IndexSizeError");
    }
    this.setAttributeNS(null, "maxlength", String(value));
  }

  get maxLength() {
    if (!this.hasAttributeNS(null, "maxlength")) {
      return 524288; // stole this from chrome
    }
    return parseInt(this.getAttributeNS(null, "maxlength"));
  }

  set minLength(value) {
    if (value < 0) {
      throw new DOMException("The index is not in the allowed range.", "IndexSizeError");
    }
    this.setAttributeNS(null, "minlength", String(value));
  }

  get minLength() {
    if (!this.hasAttributeNS(null, "minlength")) {
      return 0;
    }
    return parseInt(this.getAttributeNS(null, "minlength"));
  }

  get size() {
    if (!this.hasAttributeNS(null, "size")) {
      return 20;
    }
    return parseInt(this.getAttributeNS(null, "size"));
  }

  set size(value) {
    if (value <= 0) {
      throw new DOMException("The index is not in the allowed range.", "IndexSizeError");
    }
    this.setAttributeNS(null, "size", String(value));
  }

  get src() {
    return conversions.USVString(this.getAttributeNS(null, "src"));
  }

  set src(value) {
    this.setAttributeNS(null, "src", value);
  }

  // https://html.spec.whatwg.org/multipage/input.html#the-min-and-max-attributes
  get _minimum() {
    let min = this._defaultMinimum;
    const attr = this.getAttributeNS(null, "min");
    const convertStringToNumber = this._convertStringToNumber;
    if (attr !== null && convertStringToNumber !== undefined) {
      const parsed = convertStringToNumber(attr);
      if (!isNaN(parsed)) {
        min = parsed;
      }
    }
    return min;
  }

  get _maximum() {
    let max = this._defaultMaximum;
    const attr = this.getAttributeNS(null, "max");
    const convertStringToNumber = this._convertStringToNumber;
    if (attr !== null && convertStringToNumber !== undefined) {
      const parsed = convertStringToNumber(attr);
      if (!isNaN(parsed)) {
        max = parsed;
      }
    }
    return max;
  }

  get _defaultMinimum() {
    if (this.type === "range") {
      return 0;
    }
    return null;
  }

  get _defaultMaximum() {
    if (this.type === "range") {
      return 100;
    }
    return null;
  }

  get _parsedValue() {
    const converter = this._convertStringToNumber;
    if (converter !== undefined) {
      return converter(this.value);
    }
    return this.value;
  }

  // https://html.spec.whatwg.org/multipage/input.html#attr-input-step
  get _step() {
    let step = this._defaultStep;
    if (this.hasAttributeNS(null, "step") && !asciiCaseInsensitiveMatch(this.getAttributeNS(null, "step"), "any")) {
      const parsedStep = parseFloatingPointNumber(this.getAttributeNS(null, "step"));
      if (!isNaN(parsedStep) && parsedStep > 0) {
        step = parsedStep;
      }
    }
    return step;
  }

  // https://html.spec.whatwg.org/multipage/input.html#concept-input-step-scale
  get _stepScaleFactor() {
    const dayInMilliseconds = 24 * 60 * 60 * 1000;
    switch (this.type) {
      case "week":
        return 7 * dayInMilliseconds;
      case "date":
        return dayInMilliseconds;
      case "datetime-local":
      case "datetime":
      case "time":
        return 1000;
    }
    return 1;
  }

  // https://html.spec.whatwg.org/multipage/input.html#concept-input-step-default
  get _defaultStep() {
    if (this.type === "datetime-local" || this.type === "datetime" || this.type === "time") {
      return 60;
    }
    return 1;
  }

  // https://html.spec.whatwg.org/multipage/input.html#concept-input-min-zero
  get _stepBase() {
    const parseAttribute = attributeName => parseFloatingPointNumber(this.getAttributeNS(null, attributeName));
    if (this.hasAttributeNS(null, "min")) {
      const min = parseAttribute("min");
      if (!isNaN(min)) {
        return min;
      }
    }
    if (this.hasAttributeNS(null, "value")) {
      const value = parseAttribute("value");
      if (!isNaN(value)) {
        return value;
      }
    }
    return this._defaultStepBase;
  }

  // https://html.spec.whatwg.org/multipage/input.html#concept-input-step-default-base
  get _defaultStepBase() {
    if (this.type === "week") {
      // The start of week 1970-W01
      return 259200000;
    }
    return 0;
  }

  _attributeApplies(attribute) {
    return applicableTypesForAttribute[attribute].has(this.type);
  }

  _barredFromConstraintValidationSpecialization() {
    // https://html.spec.whatwg.org/multipage/input.html#hidden-state-(type=hidden)
    // https://html.spec.whatwg.org/multipage/input.html#reset-button-state-(type=reset)
    // https://html.spec.whatwg.org/multipage/input.html#button-state-(type=button)
    const willNotValidateTypes = new Set(["hidden", "reset", "button"]);
    // https://html.spec.whatwg.org/multipage/input.html#attr-input-readonly
    const readOnly = this.hasAttributeNS(null, "readonly");

    // https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#attr-fe-disabled
    return willNotValidateTypes.has(this.type) || readOnly;
  }

  get validity() {
    if (!this._validity) {
      this._validity = ValidityState.createImpl(this, {

        // https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#suffering-from-being-missing
        valueMissing: () => {
          if (!this.hasAttributeNS(null, "required")) {
            return false;
          }
          if (this.type === "checkbox") {
            // https://html.spec.whatwg.org/multipage/input.html#checkbox-state-(type=checkbox)
            // Constraint validation: If the element is required and its checkedness is
            // false, then the element is suffering from being missing.
            return !this.checked;
          } else if (this.type === "radio") {
            // https://html.spec.whatwg.org/multipage/input.html#radio-button-state-(type=radio)
            // Constraint validation: If an element in the radio button group is required,
            // and all of the input elements in the radio button group have a checkedness
            // that is false, then the element is suffering from being missing.
            return !this._isRadioGroupChecked();
          }
          return this.value === "";
        },

        // https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#suffering-from-being-too-long
        // https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#attr-fe-maxlength
        // jsdom has no way at the moment to emulate a user interaction, so tooLong/tooShort have
        // to be set to false.
        tooLong: () => false,

        // https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#suffering-from-being-too-short
        // https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#attr-fe-minlength
        tooShort: () => false,

        // https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#suffering-from-an-overflow
        // https://html.spec.whatwg.org/multipage/input.html#attr-input-max
        rangeOverflow: () => this._attributeApplies("max") && this._maximum !== null &&
          this._parsedValue > this._maximum,

        // https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#suffering-from-an-underflow
        // https://html.spec.whatwg.org/multipage/input.html#attr-input-min
        rangeUnderflow: () => this._attributeApplies("min") && this._minimum !== null &&
          this._parsedValue < this._minimum,

        // https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#suffering-from-a-pattern-mismatch
        patternMismatch: () => {
          if (!this.hasAttributeNS(null, "pattern") || !this._attributeApplies("pattern") || this.value === "") {
            return false;
          }
          let regExp;
          try {
            regExp = new RegExp(this.getAttributeNS(null, "pattern"), "u");
          } catch (e) {
            return false;
          }
          if (this.type === "email" && this.hasAttributeNS(null, "multiple")) {
            return splitOnCommas(this.value).every(value => regExp.test(value));
          }
          return !regExp.test(this.value);
        },

        // https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#suffering-from-a-step-mismatch
        // https://html.spec.whatwg.org/multipage/input.html#attr-input-step
        stepMismatch: () => {
          // Constraint validation: When the element has an allowed value step, and the result of applying
          // the algorithm to convert a string to a number to the string given by the element's value is a
          // number, and that number subtracted from the step base is not an integral multiple of the
          // allowed value step, the element is suffering from a step mismatch.
          if (!this._attributeApplies("step")) {
            return false;
          }
          const step = parseFloatingPointNumber(this.getAttributeNS(null, "step"));
          if (isNaN(step) || step <= 0) {
            return false;
          }

          let number = this._parsedValue;
          if (isNaN(number) || this.value === "") {
            return false;
          }
          if (this._type === "month") {
            number = parseMonthString(this.value).month - 1;
          }
          return number % (this._stepBase - (this._step * this._stepScaleFactor)) !== 0;
        },

        // https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#suffering-from-a-type-mismatch
        typeMismatch: () => {
          if (this.value === "") {
            return false;
          }
          if (this.type === "email") {
            // https://html.spec.whatwg.org/multipage/input.html#e-mail-state-(type=email)
            // Constraint validation [multiple=false]: While the value of the element is neither the empty
            // string nor a single valid e - mail address, the element is suffering from a type mismatch.

            // Constraint validation [multiple=true]: While the value of the element is not a valid e-mail address list,
            // the element is suffering from a type mismatch.
            return !isValidEmailAddress(this.value, this.hasAttributeNS(null, "multiple"));
          } else if (this.type === "url") {
            // https://html.spec.whatwg.org/multipage/input.html#url-state-(type=url)
            // Constraint validation: While the value of the element is neither the empty string
            // nor a valid absolute URL, the element is suffering from a type mismatch.
            return !isValidAbsoluteURL(this.value);
          }
          return false;
        }
      });
    }
    return this._validity;
  }

  [cloningSteps](copy, node) {
    copy._value = node._value;
    copy._checkedness = node._checkedness;
    copy._dirtyValue = node._dirtyValue;
    copy._dirtyCheckedness = node._dirtyCheckedness;
  }
}

mixin(HTMLInputElementImpl.prototype, DefaultConstraintValidationImpl.prototype);

module.exports = {
  implementation: HTMLInputElementImpl
};


'use strict';

/* eslint-disable no-console */

const fs = require('fs');
const path = require('path');
const startCase = require('lodash.startcase');
const correctAnchor = require('./correct-anchor');
const config = require('./config');

const langs = config.langs;
const docConfig = require('../config.json');
const docsDir = path.join(__dirname, '../');
const watchMode = process.argv[2] === '-w';

let cache = {};
start();

function start(){
  build();

  if (watchMode) {
    watch();
  }
}
function build() {
  sammary();
  cache = {};
  combo();
}

function watch(){
  fs.watch(docsDir, { recursive: true }, (eventType, filename) => {
    if (filename.indexOf('/_book') >= 0) {
      return;
    }
    if (filename.indexOf('/script') >= 0) {
      return;
    }

    const ignores = langs.reduce((result, lang) => {
      result.push(`${lang}/README.md`);
      result.push(`${lang}/SUMMARY.md`);
      return result;
    }, []);

    const isConfigFile = filename === 'config.json';
    if (isConfigFile) {
      console.warn('Please restart the docs:watch cmd !');
      return;
    }
    const isWatchTarget = langs.some(lang => {
      return filename.startsWith(lang) && ignores.indexOf(filename) < 0;
    });
    if (isWatchTarget) {
      console.log(`file: ${filename} type: ${eventType}`);
      build();
    }
  });

  langs.forEach((lang) => {
    console.info(`***** start watching ${docsDir}/${lang}/*.md *****`);
  });
}

function getFilename({ lang, parentPaths, slug, isDir }) {
  let filename = '';
  if (isDir) {
    filename = path.join(docsDir, lang, parentPaths.join('/'), isDir ? slug : '', 'README.md');
    if (fs.existsSync(filename)) {
      return filename;
    }
  }
  filename = path.join(docsDir, lang, parentPaths.join('/'), `${slug}.md`);
  if (fs.existsSync(filename)) {
    return filename;
  }
  return '';
}

function getFileId(arr) {
  return arr.join('_');
}

function generateNavTitle({ parentPaths, item, sign, lang }) {
  const isDir = typeof item === 'object' && item.catalog && item.catalog.length;
  const slug = typeof item === 'string' ? item : typeof item === 'object' ? item.name : '';
  const sign_ = sign || '*';

  if (!slug) {
    return null;
  }

  const filename = getFilename({
    lang, parentPaths, slug, isDir
  });
  const anchor = filename ? getFileId(parentPaths.concat(slug)) : '';
  const prefix = parentPaths.reduce((pre, next) => {
    return pre += '\t';
  }, '');
  const title = typeof item === 'string' ? prettyCatalog(item) : item.i18n ?
    item.i18n[lang] || item.i18n['en-us'] || prettyCatalog(item.name) : prettyCatalog(item.name);

  return `${prefix}${sign_} [${title}](README.md#${anchor})`;
}

function appendTitleAnchor(content, anchor) {
  return content.replace(/(\#+.*)/, (match, p1) => {
    return `${p1} {#${anchor}}`;
  });
}

function walk({ catalog, lang, result, parentPaths, fn }) {
  if (!catalog || !catalog.length) {
    return result;
  }
  return catalog.reduce((result, item) => {
    const slug = typeof item === 'string' ? item : typeof item === 'object' ? item.name : '';
    if (slug) {
      const isDir = item.catalog && item.catalog.length;
      fn({ parentPaths, lang, item, isDir, result });

      if (isDir) {
        parentPaths.push(slug);
        result = walk({ catalog: item.catalog, lang, result, parentPaths, fn });
        parentPaths.pop();
      }
    }
    return result;
  }, result);
}
/**
 * generate `SUMMARY.md` by `config.json`
 */
function sammary() {
  const nodeFn = function ({ parentPaths, lang, item, isDir, result }) {
    const navTitle = generateNavTitle({ parentPaths, item, sign: isDir ? '-' : '*', lang });
    result.push(navTitle);
  };

  langs.forEach(dir => {
    const SUMMARY = 'SUMMARY.md';
    const targetFile = path.join(docsDir, `${dir}/${SUMMARY}`);

    const result = walk({
      catalog: docConfig.catalog,
      lang: dir,
      result: [],
      parentPaths: [],
      fn: nodeFn
    });

    if (result && result.length) {
      result.unshift('# whistle\n');
      fs.writeFileSync(targetFile, result.join('\n'));
    }
  });
}

function combo() {
  const nodeFn = function ({ parentPaths, lang, item, result }) {
    const isDir = typeof item === 'object' && item.catalog && item.catalog.length;
    const slug = typeof item === 'string' ? item : typeof item === 'object' ? item.name : '';
    const filename = getFilename({
      parentPaths, lang, slug, isDir
    });
    const fileId = getFileId(parentPaths.concat(slug));

    if (filename) {
      let content = fs.readFileSync(filename).toString();
      content = appendTitleAnchor(content, fileId);
      content = correctAnchor({rootPath: path.join(docsDir, lang) , absFilename: filename, content});

      cache[fileId] = content;
      result.push(fileId);
    }
  };

  langs.forEach(dir => {
    const README = 'README.md';
    const targetFile = path.join(docsDir, `${dir}/${README}`);

    const result = walk({
      catalog: docConfig.catalog,
      lang: dir,
      result: [],
      parentPaths: [],
      fn: nodeFn
    });
    if (result && result.length) {
      const content = result.reduce((pre, next) => {
        if (cache[next]) {
          pre += cache[next] + '\n\n';
        }
        return pre;
      }, '');
      fs.writeFileSync(targetFile, content);
    }
  });
}

function isChinese(string) {
  var req = /[\u4E00-\u9FA5]|[\uFE30-\uFFA0]/gi;
  if (!req.exec(string)) {
    return false;
  } else {
    return true;
  }
}

function isNonAscii(string) {
  var regExp = /^[ -~\t\n\r]+$/gi;
  return !regExp.test(string);
}

function prettyCatalog(fileName) {
  if (isChinese(fileName) || isNonAscii(fileName)) {
    return fileName;
  }
  return startCase(fileName);
}


var net = require('net');
var util = require('./index');
var logger = require('./logger');
var config = require('../config');
var socketMgr = require('../socket-mgr');

if (!(config.reqCacheSize > 0) || config.reqCacheSize < 600) {
  config.reqCacheSize = 600;
}

if (!(config.frameCacheSize > 0) || config.frameCacheSize < 600) {
  config.frameCacheSize = 600;
}

var TIMEOUT = 36000;
var BINDING_TIMEOUT = 6000;
var CLEAR_INTERVAL = 5000;
var CACHE_TIME = 1000 * 60;
var CACHE_TIMEOUT = 1000 * 60 * 3;
var MAX_LENGTH = config.reqCacheSize;
var OVERFLOW_LENGTH = MAX_LENGTH * 3;
var MAX_CACHE_SIZE = MAX_LENGTH * 2;
var PRESERVE_LEN = 360;
var MAX_FRAMES_LENGTH = config.frameCacheSize;
var COUNT = 100;
var count = 0;
var ids = [];
var reqData = {};
var frames = [];
var proxy, binded, timeout, interval, timer;

function disable() {
  proxy.removeListener('request', handleRequest);
  proxy.removeListener('frame', handleFrame);
  ids = [];
  reqData = {};
  frames = [];
  interval && clearInterval(interval);
  interval = null;
  binded = false;
  clearTimeout(timer);
  timer = null;
}

function resetTimer() {
  timer = null;
}

function enable() {
  if (timer) {
    return;
  }
  timer = setTimeout(resetTimer, BINDING_TIMEOUT);
  if (!binded) {
    binded = true;
    proxy.on('request', handleRequest);
    proxy.on('frame', handleFrame);
  }
  clearTimeout(timeout);
  timeout = setTimeout(disable, TIMEOUT);
  if (!interval) {
    interval = setInterval(clearCache, CLEAR_INTERVAL);
  }
}

/**
* 如果超过最大缓存数，清理如下请求数据：
* 1. 已经请求结束且结束时间超过10秒
* 2. 请求#1前面的未结束且未被ui读取过的请求
*/
function clearCache() {
  var overflow = frames.length - MAX_FRAMES_LENGTH;
  overflow > 0 && frames.splice(0, overflow + 60);
  var len = ids.length;
  if (len <= MAX_LENGTH) {
    return;
  }

  var now = Date.now();
  var _ids = [];
  var preserveLen = len;
  overflow = -1;
  if (len >= OVERFLOW_LENGTH) {
    overflow = len - MAX_CACHE_SIZE;
    preserveLen = len - PRESERVE_LEN;
  }
  for (var i = 0; i < len; i++) {
    var id = ids[i];
    var curData = reqData[id];
    if (i > overflow &&
      (i >= preserveLen ||
        (curData.endTime ? now - curData.endTime < CACHE_TIME : now - curData.startTime < CACHE_TIMEOUT))) {
      if (curData.endTime && curData.abort) {
        delete curData.abort;
      }
      _ids.push(id);
    } else {
      delete reqData[id];
    }
  }
  ids = _ids;
}

function getIds(startTime, count, lastRowId) {
  startTime = startTime || lastRowId;
  if (!startTime) {
    return ids.slice(-count);
  }
  var index = 0;
  if (startTime !== '0') {
    index = ids.indexOf(startTime) + 1;
  }
  return ids.slice(index, index + count);
}

function getList(ids) {
  if (!Array.isArray(ids)) {
    return [];
  }
  return ids.map(function(id, i) {
    return reqData[id];
  });
}

function handleRequest(req) {
  var curData = req.data;
  var id = curData.id = curData.id || (curData.startTime + '-' + ++count);
  var removeAbort = function() {
    if (curData.abort) {
      delete curData.abort;
    }
  };
  req.on('end', removeAbort);
  req.on('error', removeAbort);
  req.on('abort', removeAbort);
  reqData[id] = curData;
  ids.indexOf(id) === -1 && ids.push(id);
}

function decodeData(frame) {
  if (frame.base64 == null) {
    frame.base64 = frame.bin ? frame.bin.toString('base64') : '';
    frame.bin = '';
  }
  return frame;
}
function handleFrame(data) {
  frames.push(data);
}

function getFrames(curReqId, lastFrameId) {
  if (!curReqId) {
    return;
  }
  var result = [];
  var count = 16;
  for (var i = 0, len = frames.length; i < len; i++) {
    var frame = frames[i];
    if (frame.reqId === curReqId && (!lastFrameId || frame.frameId > lastFrameId)) {
      result.push(decodeData(frame));
      if (--count <= 0) {
        return result;
      }
    }
  }
  return result;
}

function getLastFrame(curReqId) {
  for (var i = frames.length - 1; i >= 0; i--) {
    var frame = frames[i];
    if (frame.reqId === curReqId) {
      return decodeData(frame);
    }
  }
}

module.exports = function init(_proxy) {
  proxy = _proxy;
  enable();
/**
* options: {
* 		startTime: timestamp || timestamp + '-' + count
* 		count: 获取新数据的数量
* 		ids: 请未结束的id列表
* }
*
* @param options
*/
  function formatFilter(filter, clientIp) {
    if (!filter.url && !filter.name && !filter.value && !filter.ip) {
      return;
    }
    var url = util.trimStr(filter.url).toLowerCase();
    var name = util.trimStr(filter.name).toLowerCase();
    var value = util.trimStr(filter.value).toLowerCase();
    var ip = util.trimStr(filter.ip);
    var list = [];
    if (ip === 'self') {
      ip = clientIp;
    } else if (ip && !net.isIP(ip)) {
      ip.split(',').forEach(function(item) {
        item = item.trim();
        if (item === 'self') {
          item = clientIp;
        }
        if (net.isIP(item) && list.indexOf(item) === -1) {
          list.push(item);
        }
      });
      ip = null;
    }
    var result;
    if (url) {
      result = {};
      result.url = url;
    }
    if (name && value) {
      result = result || {};
      result.name = name;
      result.value = value;
    }
    if (ip) {
      result = result || {};
      result.ip = ip;
    }
    if (list.length) {
      result = result || {};
      result.ipList = list.slice(0, 16);
    }
    return result;
  }
  function checkClientIp(item, filter) {
    var clientIp = item.req.ip;
    if (filter.ip && clientIp !== filter.ip) {
      return false;
    }
    var ipList = filter.ipList;
    if (!ipList) {
      return true;
    }
    var len = ipList.length;
    if (len < 3) {
      if (ipList[0] === clientIp) {
        return true;
      }
      if (ipList[1] && ipList[1] === clientIp) {
        return true;
      }
    } else {
      for (var i = 0; i < len; i++) {
        var ip = ipList[i];
        if (ip === clientIp) {
          return true;
        }
      }
    }
    return false;
  }
  function checkItem(item, filter) {
    if (!item) {
      return false;
    }
    if (!checkClientIp(item, filter)) {
      return false;
    }
    if (filter.url && item.url.toLowerCase().indexOf(filter.url) === -1) {
      return false;
    }
    if (!filter.name) {
      return true;
    }
    var value = item.req.headers[filter.name];
    if (!value || typeof value !== 'string') {
      if (!Array.isArray(value)) {
        return false;
      }
      value = value.join('\n');
    }
    value = value.toLowerCase();
    if (value.indexOf(filter.value) !== -1) {
      return true;
    }
    return value.indexOf(util.encodeURIComponent(filter.value).toLowerCase()) !== -1;
  }
  proxy.getLastDataId = function() {
    return ids[ids.length - 1];
  };

  function toBase64String(data) {
    if (Buffer.isBuffer(data.body)) {
      data.base64 = data.body.toString('base64');
      data.body = '';
    }
  }

  proxy.enableCapture = enable;
  proxy.getItem = function(id) {
    var item = reqData[id];
    if (item) {
      toBase64String(item.req);
      toBase64String(item.res);
    }
    return item;
  };
  proxy.abortRequest = function(id) {
    var item = id && reqData[id];
    if (!item || !item.abort) {
      return;
    }
    if (item.endTime) {
      delete item.abort;
    } else {
      item.abort();
    }
  };
  proxy.getFrames = function(options) {
    return getFrames(options.curReqId, options.lastFrameId);
  };
  proxy.getData = function(options, clientIp) {
    enable();
    options = options || {};
    var filter = formatFilter(options, clientIp);
    var data = {};
    var count = options.count;
    var startTime = options.startTime;
    var clearNetwork = !(options.dumpCount > 0) && (count == 0 || startTime == -2);
    if (!clearNetwork) {
      count = count > 0 && count < COUNT ? +count : COUNT;
      if (options.dumpCount > 0) {
        var len = ids.length;
        startTime = ids[len > options.dumpCount ? len - options.dumpCount : 0];
      }
    }
    var newIds = (clearNetwork || startTime == -1) ? [] : getIds(startTime, count, options.lastRowId);
    var setData = function(item) {
      if (item) {
        var req = item.req;
        var res = item.res;
        toBase64String(req);
        toBase64String(res);
        if (config.secureFilter) {
          try {
            item = config.secureFilter(item, clientIp, filter) || item;
          } catch (e) {
            if (config.debugMode) {
              /* eslint-disable no-console */
              console.log(e);
            }
            logger.error(e);
          }
        }
        data[item.id] = item;
      }
    };
    if (newIds.length > 0) {
      if (filter) {
        var id = newIds[0];
        var index = ids.indexOf(newIds[0]);
        newIds = [];
        while(id && count > 0) {
          var item = reqData[id];
          if (checkItem(item, filter)) {
            setData(item);
            newIds.push(id);
            --count;
          }
          id = ids[++index];
        }
      } else if (!filter) {
        getList(newIds).forEach(setData);
      }
    }
    getList(options.ids).forEach(setData);
    var endId = ids[ids.length - 1];
    var lastFrameId, frames;
    if (options.lastFrameId == -3) {
      var lastFrame = getLastFrame(options.curReqId);
      if (lastFrame) {
        if (lastFrame.closed || lastFrame.err) {
          frames = [lastFrame];
        } else {
          lastFrameId = lastFrame.frameId;
        }
      }
    } else {
      frames = getFrames(options.curReqId, options.lastFrameId);
    }

    return {
      ids: options.ids || [],
      newIds: newIds,
      data: data,
      lastId: clearNetwork ? endId : newIds[newIds.length - 1],
      endId: endId,
      frames: frames,
      lastFrameId: lastFrameId,
      socketStatus: socketMgr.getStatus(options.curReqId)
    };
  };
};


var net = require('net');
var fs = require('fs');
var path = require('path');
var rules = require('./index');
var util = require('../util');
var config = require('../config');
var Storage = require('./storage');
var system = require('./system');
var httpMgr = require('../util/http-mgr');
var Buffer = require('safe-buffer').Buffer;

var INTERVAL = 1000 * 60 * 60;
var MAX_URL_LEN = 10 * 1024;
var MAX_HEADERS_LEN = 128 * 1024;
var MAX_BODY_LEN = 256 * 1024;
var MAX_METHOD_LEN = 64;
var MAX_HISTORY_LEN = 36;
var history = [];
var rulesStorage = new Storage(config.rulesDir);
var valuesStorage = new Storage(config.valuesDir);
var propertiesStorage = new Storage(config.propertiesDir);
var shadowRules = config.shadowRules;
var LINE_END_RE = /\n|\r\n|\r/g;
var MAX_REMOTE_RULES_COUNT = 12;
var REMOTE_RULES_RE = /^\s*@(`?)(whistle\.[a-z\d_\-]+(?:\/[^\s#]*)?|(?:https?:\/\/|[a-z]:[\\/]|~?\/)[^\s#]+)\s*\1(?:#.*)?$/img;
var MAX_COUNT_BY_IMPORT = 60;
var uploadFiles = [];
var MAX_FILENAME_LEN = 60;
var ILLEGAL_FILENAME_RE = /[\\/:*?"<>|\s]/;
var LOCAL_FILES = config.LOCAL_FILES;
var inlineValues;
var CONTROL_RE = /[\u001e\u001f\u200e\u200f\u200d\u200c\u202a\u202d\u202e\u202c\u206e\u206f\u206b\u206a\u206d\u206c]+/g;
var MULTI_LINE_VALUE_RE = /^[^\n\r\S]*(```+)[^\n\r\S]*(\S+)[^\n\r\S]*[\r\n]([\s\S]+?)[\r\n][^\n\r\S]*\1\s*$/mg;

try {
  history = JSON.parse(propertiesStorage.readFile('composerHistory'));
  if (Array.isArray(history)) {
    history = history.filter(checkHistory);
    history = history.slice(0, MAX_HISTORY_LEN);
  } else {
    history = [];
  }
} catch(e) {}

function checkFilename(name) {
  if (!name || typeof name !== 'string' || name.length > MAX_FILENAME_LEN) {
    return false;
  }
  return !ILLEGAL_FILENAME_RE.test(name);
}

try {
  var _files = fs.readdirSync(LOCAL_FILES)
    .filter(checkFilename);
  for (var i = 0, len = _files.length; i < len; i++) {
    var _name = _files[i];
    try {
      var stat = fs.statSync(path.join(LOCAL_FILES, _name));
      if (stat.isFile) {
        uploadFiles.push({
          name: _name,
          date: stat.mtime.getTime()
        });
      }
    } catch (e) {}
    if (uploadFiles.length >= MAX_FILENAME_LEN) {
      break;
    }
  }
  if (uploadFiles.length) {
    uploadFiles.sort(function(prev, next) {
      return prev.date > next.date ? -1 : 1;
    });
  }
} catch (e) {}

function checkHistory(data) {
  return typeof data.url === 'string' && typeof data.method === 'string'
   && typeof data.headers === 'string' && typeof data.body === 'string';
}

process.nextTick(function() {
  parseRules();
});
/**
* rules
*/

function resolveInlineValues(str) {
  str = str && str.replace(CONTROL_RE, '').trim();
  if (!str || str.indexOf('```') === -1) {
    return str;
  }
  return str.replace(MULTI_LINE_VALUE_RE, function(_, __, key, value) {
    inlineValues = inlineValues || {};
    if (!inlineValues[key]) {
      inlineValues[key] = value;
    }
    return '';
  });
}

function parseHosts(text) {
  if (typeof text != 'string'
|| !(text = text.trim())) {
    return '';
  }
  var result = [];
  text.split(/\n|\r\n|\r/g)
.forEach(function(line) {
  line = line.replace(/#.*$/, '').trim();
  if (!line) {
    return;
  }
  line = line.split(/\s+/);
  var pattern = line[0];
  if (net.isIP(pattern)) {
    line.slice(1).forEach(function(matcher) {
      !/\//.test(matcher) && result.push(pattern + ' ' + matcher);
    });
  } else if (!/\//.test(pattern) && line[1] && net.isIP(line[1])) {
    result.push(line[1] + ' ' + pattern);
  }
});

  return result.join('\r\n');
}

function reverseRules(text, orig) {
  if (!text) {
    return '';
  }
  text = resolveInlineValues(text);
  text = text.split(LINE_END_RE).reverse();
  return orig ? text : text.join('\n');
}

function parseRules() {
  var rulesText, backRulesFirst;
  if (propertiesStorage.getProperty('disabledAllRules')) {
    rulesText = config.allowDisableShadowRules ? null : shadowRules;
  } else {
    var value = [];
    var hosts =  propertiesStorage.getProperty('syncWithSysHosts') ? [] : null;
    if (!config.multiEnv) {
      getAllRulesFile().forEach(function(file) {
        if (file.selected) {
          value.push(file.data);
          if (hosts) {
            var sysHosts = parseHosts(file.data);
            if (sysHosts) {
              hosts.push('#\r\n# ' + file.name + '\r\n#' + '\r\n' + sysHosts);
            }
          }
        }
      });
    }
    backRulesFirst = propertiesStorage.getProperty('backRulesFirst') === true;
    var defaultRules = defaultRulesIsDisabled() ? null : getDefaultRules();
    if (defaultRules) {
      if (backRulesFirst) {
        value.unshift(defaultRules);
      } else {
        value.push(defaultRules);
      }
    }

    if (hosts && defaultRules) {
      var sysHosts = parseHosts(defaultRules);
      if (sysHosts) {
        hosts.push('#\r\n# Default\r\n#' + '\r\n' + sysHosts);
      }
    }
    if (backRulesFirst) {
      value = reverseRules(value.join('\n'), true);
    }
    value = value && value.join('\r\n');
    if (shadowRules) {
      if (backRulesFirst) {
        value = reverseRules(shadowRules) + '\n' + value;
      } else {
        value += '\n' + shadowRules;
      }
    }
    rulesText = value;
    if (hosts) {
      hosts = hosts.join('\r\n\r\n');
      system.setHosts(hosts, util.noop);
    }
  }
  var index = 0;
  if (rulesText) {
    rulesText = rulesText.replace(REMOTE_RULES_RE, function(_, apo, rulesUrl) {
      if (index >= MAX_REMOTE_RULES_COUNT) {
        return '';
      }
      ++index;
      rulesUrl = util.getPluginRulesUrl(rulesUrl);
      if (apo) {
        rulesUrl = util.setConfigVar(rulesUrl);
      }
      var remoteRules = httpMgr.add(rulesUrl);
      return backRulesFirst ? reverseRules(remoteRules) : remoteRules;
    });
  }
  if (!index) {
    httpMgr.clean();
  }
  rules.parse(rulesText, null, inlineValues);
  inlineValues = null;
}

httpMgr.addChangeListener(parseRules);

exports.parseRules = parseRules;

function setDefaultRules(data) {
  data = typeof data != 'string' ? '' : data;
  var oldData = rulesStorage.getProperty('defalutRules') || '';
  rulesStorage.setProperty('defalutRules', data);
  parseRules();
  return data !== oldData;
}

function getDefaultRules() {
  return rulesStorage.getProperty('defalutRules');
}

function disableDefaultRules() {
  rulesStorage.setProperty('disabledDefalutRules', true);
  parseRules();
}

function enableDefaultRules() {
  rulesStorage.setProperty('disabledDefalutRules', false);
  parseRules();
}

function defaultRulesIsDisabled() {
  return rulesStorage.getProperty('disabledDefalutRules');
}

function selectRulesFile(file) {
  if (!rulesStorage.existsFile(file) || config.multiEnv) {
    return;
  }

  var selectedList = allowMultipleChoice() ? getSelectedRulesList() : [];
  if (selectedList.indexOf(file) == -1) {
    selectedList.push(file);
    rulesStorage.setProperty('selectedList', selectedList);
  }
  parseRules();
  return selectedList;
}

function unselectRulesFile(file, force) {
  if (!force && config.multiEnv) {
    return;
  }
  var selectedList = getSelectedRulesList();
  var index = selectedList.indexOf(file);
  if (index != -1) {
    selectedList.splice(index, 1);
    rulesStorage.setProperty('selectedList', selectedList);
  }
  parseRules();

  return selectedList;
}

function allowMultipleChoice() {
  return propertiesStorage.getProperty('allowMultipleChoice');
}

function clearSelection() {
  rulesStorage.setProperty('selectedList', []);
  parseRules();
}

function getSelectedRulesList() {
  if (config.multiEnv) {
    return [];
  }
  var selectedList = rulesStorage.getProperty('selectedList');
  if (!Array.isArray(selectedList)) {
    selectedList = [];
    rulesStorage.setProperty('selectedList', selectedList);
  }
  return selectedList;
}

function removeRulesFile(file) {
  unselectRulesFile(file, true);
  return rulesStorage.removeFile(file);
}

function renameRulesFile(file, newFile) {
  if (!rulesStorage.renameFile(file, newFile)) {
    return;
  }

  var selectedList = getSelectedRulesList();
  var index = selectedList.indexOf(file);
  if (index != -1) {
    selectedList[index] = newFile;
    rulesStorage.setProperty('selectedList', selectedList);
  }
  return true;
}

function addRulesFile(file, data) {
  return rulesStorage.writeFile(file, data);
}

function getAllRulesFile() {
  var list = rulesStorage.getFileList();
  var selectedList = getSelectedRulesList();
  list.forEach(function(file) {
    file.selected = selectedList.indexOf(file.name) != -1;
  });
  return list;
}

function resetRulesIfResort(fromName, toName) {
  var selectedList = getSelectedRulesList();
  if (selectedList.indexOf(fromName) == -1 && selectedList.indexOf(toName) == -1) {
    return;
  }
  parseRules();
}

exports.rules = {
  enableBackRulesFirst: function(backRulesFirst) {
    var curFlag = propertiesStorage.getProperty('backRulesFirst') === true;
    if (curFlag !== backRulesFirst) {
      propertiesStorage.setProperty('backRulesFirst', backRulesFirst);
      parseRules();
    }
  },
  moveTo: function(fromName, toName, clientId) {
    if (rulesStorage.moveTo(fromName, toName)) {
      resetRulesIfResort(fromName, toName);
      config.setModified(clientId, true);
      return true;
    }
  },
  get: function(file) {
    return rulesStorage.readFile(file);
  },
  getSysHosts: system.getHosts,
  setSysHosts: system.setHosts,
  remove: function(file, clientId) {
    if (removeRulesFile(file)) {
      config.setModified(clientId, true);
    }
  },
  add: function(file, data, clientId) {
    if (addRulesFile(file, data)) {
      config.setModified(clientId, true);
    }
  },
  rename: function(file, newFile, clientId) {
    if (renameRulesFile(file, newFile)) {
      config.setModified(clientId, true);
    }
  },
  select: selectRulesFile,
  unselect: unselectRulesFile,
  list: getAllRulesFile,
  getDefault: getDefaultRules,
  setDefault: function(value, clientId) {
    if (setDefaultRules(value)) {
      config.setModified(clientId, true);
    }
  },
  enableDefault: enableDefaultRules,
  disableDefault: disableDefaultRules,
  defaultRulesIsDisabled: defaultRulesIsDisabled,
  parseRules: parseRules,
  clearSelection:clearSelection,
  getSelectedList: getSelectedRulesList
};

/**
* values
*/

function addValuesFile(file, data) {
  return valuesStorage.writeFile(file, data);
}

function indexOfUploadFiles(name) {
  for (var i = 0, len = uploadFiles.length; i < len; i++) {
    var file = uploadFiles[i];
    if (file.name === name) {
      return i;
    }
  }
  return -1;
}

exports.values = {
  getUploadFiles: function() {
    return uploadFiles;
  },
  download: function(name, res) {
    if (!checkFilename(name)) {
      return res.end();
    }
    res.download(path.join(LOCAL_FILES, name), name);
  },
  existsFile: function(name) {
    return name && indexOfUploadFiles(name) !== -1;
  },
  LIMIMT_FILES_COUNT: MAX_FILENAME_LEN,
  addUploadFile: function(options, callback) {
    var name = options.name;
    if (!checkFilename(name)) {
      return callback();
    }
    if (uploadFiles.length >= MAX_FILENAME_LEN
        && indexOfUploadFiles(name) === -1) {
      return callback(new Error('The number of uploaded files cannot exceed 60.'));
    }
    var content = '';
    var base64 = options.base64;
    var headers = options.headers;

    if (headers && typeof headers === 'string') {
      try {
        content = Buffer.from(headers + '\r\n\r\n');
      } catch (e) {
        return callback();
      }
    }

    if (base64 && typeof base64 === 'string') {
      try {
        base64 = Buffer.from(base64, 'base64');
        content = content ? Buffer.concat([content, base64]) : base64;
      } catch (e) {
        return callback();
      }
    }
    if (!content) {
      return callback();
    }
    fs.writeFile(path.join(LOCAL_FILES, name), content, function(err) {
      if (!err) {
        var index = indexOfUploadFiles(name);
        if (index !== -1) {
          uploadFiles.splice(index, 1);
        }
        uploadFiles.unshift({
          name: name,
          date: Date.now()
        });
      }
      callback(err);
    });
  },
  removeUploadFile: function(name, callback) {
    if (!checkFilename(name) || indexOfUploadFiles(name) === -1) {
      return callback();
    }
    fs.unlink(path.join(LOCAL_FILES, name), function(err) {
      var index = indexOfUploadFiles(name);
      if (index === -1 || !err || err.code === 'ENOENT') {
        if (index !== -1) {
          uploadFiles.splice(index, 1);
        }
        return callback();
      }
      callback(err);
    });
  },
  moveTo: function(fromName, toName, clientId) {
    if (valuesStorage.moveTo(fromName, toName)) {
      config.setModified(clientId);
      return true;
    }
  },
  add: function(file, data, clientId) {
    if (addValuesFile(file, data)) {
      config.setModified(clientId);
    }
  },
  get: function(file) {
    return valuesStorage.readFile(file);
  },
  remove: function remove(file, clientId) {
    if (valuesStorage.removeFile(file)) {
      config.setModified(clientId);
    }
  },
  rename: function(file, newFile, clientId) {
    if (valuesStorage.renameFile(file, newFile)) {
      config.setModified(clientId);
    }
  },
  list: function list() {
    var selectedFile = valuesStorage.getProperty('selectedFile');
    var list = valuesStorage.getFileList();
    if (selectedFile) {
      list.forEach(function(file) {
        file.selected = file.name == selectedFile;
      });
    }
    return list;
  },
  select: function(file) {
    typeof file == 'string' && valuesStorage.setProperty('selectedFile', file);
  },
  unselect: function() {
    valuesStorage.removeProperty('selectedFile');
  }
};

setTimeout(function getWhistleVersion() {
  util.getLatestVersion(config.registry, function(ver) {
    ver && propertiesStorage.setProperty('latestVersion', ver);
    setTimeout(getWhistleVersion, INTERVAL);
  });
}, 1000); //等待package的信息配置更新完成

/**
* properties
*/
var composerTimer;
function saveComposerHistory() {
  composerTimer = null;
  try {
    propertiesStorage.writeFile('composerHistory', JSON.stringify(history));
  } catch(e) {}
}

exports.properties = {
  set: function(name, value) {
    typeof name == 'string' ? propertiesStorage.setProperty(name, value) :
propertiesStorage.setProperties(name);
  },
  remove: function(name) {
    propertiesStorage.removeProperty(name);
  },
  get: function(name) {
    return propertiesStorage.getProperty(name);
  },
  getHistory: function() {
    return history;
  },
  addHistory: function(data) {
    if (!data.needResponse || !checkHistory(data)) {
      return;
    }
    var url = data.url;
    var method = data.method;
    var headers = data.headers;
    var body = data.body;
    var result = {
      date: Date.now(),
      url: url.length > MAX_URL_LEN ? url.substring(0, MAX_URL_LEN) : url,
      method: method.length > MAX_METHOD_LEN ? method.substring(0, MAX_METHOD_LEN) : method,
      headers: headers.length > MAX_HEADERS_LEN ? headers.substring(0, MAX_HEADERS_LEN) : headers,
      body: body.length > MAX_BODY_LEN ? body.substring(0, MAX_BODY_LEN) : body,
      isHexText: !!data.isHexText
    };
    for (var i = 0, len = history.length; i < len; i++) {
      var item = history[i];
      if (item.url === result.url && item.method === result.method
        && item.headers === result.headers && item.body === result.body) {
        history.splice(i, 1);
        break;
      }
    }
    history.unshift(result);
    var overflow = history.length - MAX_HISTORY_LEN;
    if (overflow > 0) {
      history.splice(MAX_HISTORY_LEN, overflow);
    }
    if (!composerTimer) {
      composerTimer = setTimeout(saveComposerHistory, 2000);
    }
  }
};

function getRules(rules) {
  if (Array.isArray(rules)) {
    return rules.join('\n');
  }
  if (typeof rules === 'string') {
    return rules;
  }
}

exports.addRules = function(rules, replace, clientId) {
  if (rules == null) {
    return;
  }
  replace = replace !== false;
  var hasChanged;
  if (Array.isArray(rules) || typeof rules == 'string') {
    if (replace !== false || !getDefaultRules()) {
      hasChanged = setDefaultRules(getRules(rules));
    }
  } else {
    var keys = Object.keys(rules).slice(keys, MAX_COUNT_BY_IMPORT);
    keys.forEach(function(name) {
      var item = name ? rules[name] : null;
      if (Array.isArray(item) || typeof item === 'string') {
        item = { rules: item };
      }
      if (item) {
        item.rules = getRules(item.rules);
        if (typeof item.replace !== 'boolean') {
          item.replace = replace;
        }
        if (name === 'Default') {
          if (typeof item.rules === 'string'
            && (item.replace !== false || !getDefaultRules())) {
            if (setDefaultRules(item.rules)) {
              hasChanged = true;
            }
          }
          if (item.enable) {
            enableDefaultRules();
          } else if (item.enable === false) {
            disableDefaultRules();
          }
        } else {
          if (typeof item.rules === 'string'
            && (item.replace !== false || !rulesStorage.existsFile(name))) {
            if (addRulesFile(name, item.rules)) {
              hasChanged = true;
            }
          }
          if (item.enable) {
            selectRulesFile(name);
          } else if (item.enable === false) {
            unselectRulesFile(name);
          }
        }
      }
    });
  }
  if (hasChanged) {
    config.setModified(clientId, true);
  }
};

exports.addValues = function(values, replace, clientId) {
  if (values == null || Array.isArray(values)) {
    return;
  }
  replace = replace !== false;
  var hasChanged;
  var keys = Object.keys(values).slice(0, MAX_COUNT_BY_IMPORT);
  keys.forEach(function(name) {
    if (/\s/.test(name) || (!replace && valuesStorage.existsFile(name))) {
      return;
    }
    var value = name ? values[name] : null;
    if (value == null) {
      return;
    }
    if (typeof value !== 'string') {
      value = JSON.stringify(value, null, '  ');
    }
    if (addValuesFile(name, value)) {
      hasChanged = true;
    }
  });
  if (hasChanged) {
    config.setModified(clientId);
  }
};


;(function() {
  if (typeof window === 'undefined' || typeof Image === 'undefined') {
    return;
  }

  if (window._whistleConsole) {
    return;
  }
  var console = window.console = window.console || {};
  var wConsole = window._whistleConsole = {};
  var JSON = window.JSON || patchJSON();
  function patchJSON() {
    var JSON = {};
    var rx_escapable = /[\\\"\u0000-\u001f\u007f-\u009f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g;

    function f(n) {
      return n < 10
              ? '0' + n
              : n;
    }

    function this_value() {
      return this.valueOf();
    }

    if (typeof Date.prototype.toJSON !== 'function') {

      Date.prototype.toJSON = function () {

        return isFinite(this.valueOf())
                  ? this.getUTCFullYear() + '-' +
                          f(this.getUTCMonth() + 1) + '-' +
                          f(this.getUTCDate()) + 'T' +
                          f(this.getUTCHours()) + ':' +
                          f(this.getUTCMinutes()) + ':' +
                          f(this.getUTCSeconds()) + 'Z'
                  : null;
      };

      Boolean.prototype.toJSON = this_value;
      Number.prototype.toJSON = this_value;
      String.prototype.toJSON = this_value;
    }

    var gap,
      indent,
      meta,
      rep;

    function quote(string) {

      rx_escapable.lastIndex = 0;
      return rx_escapable.test(string)
              ? '"' + string.replace(rx_escapable, function (a) {
                var c = meta[a];
                return typeof c === 'string'
                      ? c
                      : '\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4);
              }) + '"'
              : '"' + string + '"';
    }

    function str(key, holder) {

      var i,
        k,
        v,
        length,
        mind = gap,
        partial,
        value = holder[key];

      if (value && typeof value === 'object' &&
                  typeof value.toJSON === 'function') {
        value = value.toJSON(key);
      }

      if (typeof rep === 'function') {
        value = rep.call(holder, key, value);
      }

      switch (typeof value) {
      case 'string':
        return quote(value);

      case 'number':

        return isFinite(value)
                  ? String(value)
                  : 'null';

      case 'boolean':
      case 'null':

        return String(value);

      case 'object':

        if (!value) {
          return 'null';
        }

        gap += indent;
        partial = [];

        if (Object.prototype.toString.apply(value) === '[object Array]') {

          length = value.length;
          for (i = 0; i < length; i += 1) {
            partial[i] = str(i, value) || 'null';
          }

          v = partial.length === 0
                      ? '[]'
                      : gap
                          ? '[\n' + gap + partial.join(',\n' + gap) + '\n' + mind + ']'
                          : '[' + partial.join(',') + ']';
          gap = mind;
          return v;
        }

        if (rep && typeof rep === 'object') {
          length = rep.length;
          for (i = 0; i < length; i += 1) {
            if (typeof rep[i] === 'string') {
              k = rep[i];
              v = str(k, value);
              if (v) {
                partial.push(quote(k) + (
                                  gap
                                      ? ': '
                                      : ':'
                              ) + v);
              }
            }
          }
        } else {

          for (k in value) {
            if (Object.prototype.hasOwnProperty.call(value, k)) {
              v = str(k, value);
              if (v) {
                partial.push(quote(k) + (
                                  gap
                                      ? ': '
                                      : ':'
                              ) + v);
              }
            }
          }
        }

        v = partial.length === 0
                  ? '{}'
                  : gap
                      ? '{\n' + gap + partial.join(',\n' + gap) + '\n' + mind + '}'
                      : '{' + partial.join(',') + '}';
        gap = mind;
        return v;
      }
    }

    if (typeof JSON.stringify !== 'function') {
      meta = {
        '\b': '\\b',
        '\t': '\\t',
        '\n': '\\n',
        '\f': '\\f',
        '\r': '\\r',
        '"': '\\"',
        '\\': '\\\\'
      };
      JSON.stringify = function (value, replacer, space) {

        var i;
        gap = '';
        indent = '';

        if (typeof space === 'number') {
          for (i = 0; i < space; i += 1) {
            indent += ' ';
          }

        } else if (typeof space === 'string') {
          indent = space;
        }

        rep = replacer;
        if (replacer && typeof replacer !== 'function' &&
                      (typeof replacer !== 'object' ||
                      typeof replacer.length !== 'number')) {
          throw new Error('JSON.stringify');
        }

        return str('', {'': value});
      };
    }

    return JSON;
  }

  function stringify(obj) {
    if (typeof obj === 'function') {
      return obj.toString();
    }

    if (obj instanceof Error) {
      var stack = obj.stack;
      if (stack && typeof stack === 'string') {
        if (obj.message && stack.indexOf(obj.message) === -1) {
          return 'Error: ' + obj.message + '\n' + stack;
        }
      }
      return 'Error: ' + obj.message;
    }

    return obj === undefined ? 'undefined' : obj;
  }

  var index = 0;
  var MAX_LEN = 1024 * 56;
  function addLog(level, text) {
    var img = new Image();
    var timer;
    if (index > 9999) {
      index = 0;
    }
    var logStr = encodeURIComponent(text && (text + ''));
    if (logStr.length > MAX_LEN) {
      logStr = logStr.substring(0, MAX_LEN);
      var percIndex = logStr.indexOf('%', MAX_LEN - 3);
      if (percIndex !== -1) {
        logStr = logStr.substring(0, percIndex);
      }
    }
    img.src ='$LOG_CGI?id=$LOG_ID&level=' + level + '&text=' + logStr
      + '&' + new Date().getTime() + '-' + ++index;
    var preventGC = function() {
      img.onload = img.onerror = null;
      clearTimeout(timer);
    };
    img.onload = img.onerror = preventGC;
    timer = setTimeout(preventGC, 3000);
    if (typeof window.onWhistleLogSend === 'function') {
      window.onWhistleLogSend(level, text);
    }
  }

  function getPageInfo() {
    return '\r\nPage Url: ' + location.href + '\r\nUser Agent: ' + navigator.userAgent;
  }

  function getErrorStack(error, message) {
    var stack = (error.stack || error.message) + '';
    message = message || error.message;
    if (typeof message === 'string') {
      var msg = message.substring(message.indexOf(':') + 1);
      if (stack.indexOf(msg) === -1) {
        stack = message + '\n' + stack;
      }
    }
    return stack + getPageInfo();
  }

  function arrayIndexOf(arr, value) {
    if (arr.indexOf) {
      return arr.indexOf(value);
    }
    for (var i = 0, len = arr.length; i < len; i++) {
      if (arr[i] === value) {
        return i;
      }
    }
    return -1;
  }

  function stringifyObj(obj) {
    try {
      return JSON.stringify(obj);
    } catch(e) {}
    try {
      var keyList = [];
      var valList = [];
      return JSON.stringify(obj, function(key, value) {
        if (value && typeof value === 'object') {
          var index = arrayIndexOf(valList, value);
          valList.push(value);
          keyList.push(key);
          if (index !== -1) {
            return '[Circular ' + keyList[index] + ']';
          }
        }
        return value;
      });
    } catch(e) {}
  }

  var levels = ['fatal', 'error', 'warn', 'info', 'debug', 'log'];
  var noop = function() {};
  var slice = Array.prototype.slice;
  for (var i = 0, len = levels.length; i < len; i++) {
    (function(level) {
      var fn = console[level] || noop;
      var pending;
      var wFn = wConsole[level] = function() {
        var result = slice.call(arguments);
        if (typeof window.onBeforeWhistleLogSend === 'function') {
          pending = true;
          try {
            window.onBeforeWhistleLogSend(result, level);
          } catch(e) {
            result.push('onBeforeWhistleLogSend' + stringify(e));
          } finally {
            pending = false;
          }
        }
        for (var i = 0, len = result.length; i < len; i++) {
          result[i] = stringify(result[i]);
        }
        if (!result.length) {
          result = ['undefined'];
        }
        result = stringifyObj(result);
        result && addLog(level, result);
      };
      console[level] = function() {
        if (pending) {
          return;
        }
        pending = true;
        var weinreFn = console['_weinre_' + level];
        if (typeof weinreFn === 'function') {
          try {
            weinreFn.apply(this, arguments);
          } catch (e) {}
        }
        wFn.apply(null, arguments);
        try {
          fn.apply(this, arguments);
        } catch(e) {
          fn(arguments.length < 2 ? arguments[0] : slice.apply(arguments));
        } finally {
          pending = false;
        }
      };
    })(levels[i]);
  }
  /*eslint no-console: "off"*/
  var onerror = function(message, filename, lineno, colno, error) {
    if (error) {
      wConsole.error(getErrorStack(error, message));
    } else {
      wConsole.error('Error: ' + message + '(' + filename
          + ':' + lineno + ':' + (colno || 0) + ')' + getPageInfo());
    }
  };
  var isWeinreConsole = function(curConsole) {
    if (curConsole === console || typeof curConsole.log !== 'function') {
      return;
    }
    return curConsole.log.toString().indexOf('this._generic(MessageLevel.Log,') !== -1;
  };
  var attachOnError = function() {
    if (window.onerror !== onerror) {
      window.onerror = onerror;
    }
    var curConsole = window.console;
    if (!curConsole) {
      window.console = console;
    } else if (isWeinreConsole(curConsole)) {
      for (var i = 0, len = levels.length; i < len; i++) {
        var level = levels[i];
        var fn = console[level];
        var curFn = curConsole[level];
        if (fn !== curFn) {
          console['_weinre_' + level] = curFn;
          curConsole[level] = fn;
        }
      }
    }
    setTimeout(attachOnError, 600);
  };
  attachOnError();
})();


'use strict';

exports.__esModule = true;
exports['default'] = undefined;

var _keys = require('babel-runtime/core-js/object/keys');

var _keys2 = _interopRequireDefault(_keys);

var _classCallCheck2 = require('babel-runtime/helpers/classCallCheck');

var _classCallCheck3 = _interopRequireDefault(_classCallCheck2);

var _possibleConstructorReturn2 = require('babel-runtime/helpers/possibleConstructorReturn');

var _possibleConstructorReturn3 = _interopRequireDefault(_possibleConstructorReturn2);

var _inherits2 = require('babel-runtime/helpers/inherits');

var _inherits3 = _interopRequireDefault(_inherits2);

var _extends2 = require('babel-runtime/helpers/extends');

var _extends3 = _interopRequireDefault(_extends2);

var _class, _temp;

var _react = require('react');

var _react2 = _interopRequireDefault(_react);

var _propTypes = require('prop-types');

var _propTypes2 = _interopRequireDefault(_propTypes);

var _JSONArrow = require('./JSONArrow');

var _JSONArrow2 = _interopRequireDefault(_JSONArrow);

var _getCollectionEntries = require('./getCollectionEntries');

var _getCollectionEntries2 = _interopRequireDefault(_getCollectionEntries);

var _JSONNode = require('./JSONNode');

var _JSONNode2 = _interopRequireDefault(_JSONNode);

var _ItemRange = require('./ItemRange');

var _ItemRange2 = _interopRequireDefault(_ItemRange);

function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }

/**
 * Renders nested values (eg. objects, arrays, lists, etc.)
 */

function renderChildNodes(props, from, to) {
  var nodeType = props.nodeType,
      data = props.data,
      collectionLimit = props.collectionLimit,
      circularCache = props.circularCache,
      keyPath = props.keyPath,
      postprocessValue = props.postprocessValue,
      sortObjectKeys = props.sortObjectKeys;

  var childNodes = [];

  (0, _getCollectionEntries2['default'])(nodeType, data, sortObjectKeys, collectionLimit, from, to).forEach(function (entry) {
    if (entry.to) {
      childNodes.push(_react2['default'].createElement(_ItemRange2['default'], (0, _extends3['default'])({}, props, {
        key: 'ItemRange--' + entry.from + '-' + entry.to,
        from: entry.from,
        to: entry.to,
        renderChildNodes: renderChildNodes
      })));
    } else {
      var key = entry.key,
          value = entry.value;

      var isCircular = circularCache.indexOf(value) !== -1;

      var node = _react2['default'].createElement(_JSONNode2['default'], (0, _extends3['default'])({}, props, { postprocessValue: postprocessValue, collectionLimit: collectionLimit }, {
        key: 'Node--' + key,
        keyPath: [key].concat(keyPath),
        value: postprocessValue(value),
        circularCache: [].concat(circularCache, [value]),
        isCircular: isCircular,
        hideRoot: false
      }));

      if (node !== false) {
        childNodes.push(node);
      }
    }
  });

  return childNodes;
}

function getStateFromProps(props) {
  // calculate individual node expansion if necessary
  var expanded = props.shouldExpandNode && !props.isCircular ? props.shouldExpandNode(props.keyPath, props.data, props.level) : false;
  return {
    expanded: expanded
  };
}

var JSONNestedNode = (_temp = _class = function (_React$Component) {
  (0, _inherits3['default'])(JSONNestedNode, _React$Component);

  function JSONNestedNode(props) {
    (0, _classCallCheck3['default'])(this, JSONNestedNode);

    var _this = (0, _possibleConstructorReturn3['default'])(this, _React$Component.call(this, props));

    _this.handleClick = function () {
      return _this.setState({ expanded: !_this.state.expanded });
    };

    _this.state = getStateFromProps(props);
    return _this;
  }

  JSONNestedNode.prototype.componentWillReceiveProps = function componentWillReceiveProps(nextProps) {
    var nextState = getStateFromProps(nextProps);
    if (getStateFromProps(this.props).expanded !== nextState.expanded) {
      this.setState(nextState);
    }
  };

  JSONNestedNode.prototype.shouldComponentUpdate = function shouldComponentUpdate(nextProps, nextState) {
    var _this2 = this;

    return !!(0, _keys2['default'])(nextProps).find(function (key) {
      return key !== 'circularCache' && (key === 'keyPath' ? nextProps[key].join('/') !== _this2.props[key].join('/') : nextProps[key] !== _this2.props[key]);
    }) || nextState.expanded !== this.state.expanded;
  };

  JSONNestedNode.prototype.render = function render() {
    var _props = this.props,
        getItemString = _props.getItemString,
        nodeTypeIndicator = _props.nodeTypeIndicator,
        nodeType = _props.nodeType,
        data = _props.data,
        hideRoot = _props.hideRoot,
        createItemString = _props.createItemString,
        styling = _props.styling,
        collectionLimit = _props.collectionLimit,
        keyPath = _props.keyPath,
        labelRenderer = _props.labelRenderer,
        expandable = _props.expandable;
    var expanded = this.state.expanded;

    var renderedChildren = expanded || hideRoot && this.props.level === 0 ? renderChildNodes((0, _extends3['default'])({}, this.props, { level: this.props.level + 1 })) : null;

    var itemType = _react2['default'].createElement(
      'span',
      styling('nestedNodeItemType', expanded),
      nodeTypeIndicator
    );
    var renderedItemString = getItemString(nodeType, data, itemType, createItemString(data, collectionLimit));
    var stylingArgs = [keyPath, nodeType, expanded, expandable];

    return hideRoot ? _react2['default'].createElement(
      'li',
      styling.apply(undefined, ['rootNode'].concat(stylingArgs)),
      _react2['default'].createElement(
        'ul',
        styling.apply(undefined, ['rootNodeChildren'].concat(stylingArgs)),
        renderedChildren
      )
    ) : _react2['default'].createElement(
      'li',
      styling.apply(undefined, ['nestedNode'].concat(stylingArgs)),
      expandable && _react2['default'].createElement(_JSONArrow2['default'], {
        styling: styling,
        nodeType: nodeType,
        expanded: expanded,
        onClick: this.handleClick
      }),
      _react2['default'].createElement(
        'label',
        (0, _extends3['default'])({}, styling.apply(undefined, [['label', 'nestedNodeLabel']].concat(stylingArgs)), {
          onClick: expandable && this.handleClick
        }),
        labelRenderer.apply(undefined, stylingArgs)
      ),
      _react2['default'].createElement(
        'span',
        (0, _extends3['default'])({}, styling.apply(undefined, ['nestedNodeItemString'].concat(stylingArgs)), {
          onClick: expandable && this.handleClick
        }),
        renderedItemString
      ),
      _react2['default'].createElement(
        'ul',
        styling.apply(undefined, ['nestedNodeChildren'].concat(stylingArgs)),
        renderedChildren
      )
    );
  };

  return JSONNestedNode;
}(_react2['default'].Component), _class.propTypes = {
  getItemString: _propTypes2['default'].func.isRequired,
  nodeTypeIndicator: _propTypes2['default'].any,
  nodeType: _propTypes2['default'].string.isRequired,
  data: _propTypes2['default'].any,
  hideRoot: _propTypes2['default'].bool.isRequired,
  createItemString: _propTypes2['default'].func.isRequired,
  styling: _propTypes2['default'].func.isRequired,
  collectionLimit: _propTypes2['default'].number,
  keyPath: _propTypes2['default'].arrayOf(_propTypes2['default'].oneOfType([_propTypes2['default'].string, _propTypes2['default'].number])).isRequired,
  labelRenderer: _propTypes2['default'].func.isRequired,
  shouldExpandNode: _propTypes2['default'].func,
  level: _propTypes2['default'].number.isRequired,
  sortObjectKeys: _propTypes2['default'].oneOfType([_propTypes2['default'].func, _propTypes2['default'].bool]),
  isCircular: _propTypes2['default'].bool,
  expandable: _propTypes2['default'].bool
}, _class.defaultProps = {
  data: [],
  circularCache: [],
  level: 0,
  expandable: true
}, _temp);
exports['default'] = JSONNestedNode;


'use strict';

exports.__esModule = true;

var _extends2 = require('babel-runtime/helpers/extends');

var _extends3 = _interopRequireDefault(_extends2);

var _objectWithoutProperties2 = require('babel-runtime/helpers/objectWithoutProperties');

var _objectWithoutProperties3 = _interopRequireDefault(_objectWithoutProperties2);

var _getIterator2 = require('babel-runtime/core-js/get-iterator');

var _getIterator3 = _interopRequireDefault(_getIterator2);

var _isSafeInteger = require('babel-runtime/core-js/number/is-safe-integer');

var _isSafeInteger2 = _interopRequireDefault(_isSafeInteger);

exports['default'] = JSONIterableNode;

var _react = require('react');

var _react2 = _interopRequireDefault(_react);

var _JSONNestedNode = require('./JSONNestedNode');

var _JSONNestedNode2 = _interopRequireDefault(_JSONNestedNode);

function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }

// Returns the "n Items" string for this node,
// generating and caching it if it hasn't been created yet.
function createItemString(data, limit) {
  var count = 0;
  var hasMore = false;
  if ((0, _isSafeInteger2['default'])(data.size)) {
    count = data.size;
  } else {
    for (var _iterator = data, _isArray = Array.isArray(_iterator), _i = 0, _iterator = _isArray ? _iterator : (0, _getIterator3['default'])(_iterator);;) {
      var _ref;

      if (_isArray) {
        if (_i >= _iterator.length) break;
        _ref = _iterator[_i++];
      } else {
        _i = _iterator.next();
        if (_i.done) break;
        _ref = _i.value;
      }

      var entry = _ref;
      // eslint-disable-line no-unused-vars
      if (limit && count + 1 > limit) {
        hasMore = true;
        break;
      }
      count += 1;
    }
  }
  return '' + (hasMore ? '>' : '') + count + ' ' + (count !== 1 ? 'entries' : 'entry');
}

// Configures <JSONNestedNode> to render an iterable
function JSONIterableNode(_ref2) {
  var props = (0, _objectWithoutProperties3['default'])(_ref2, []);

  return _react2['default'].createElement(_JSONNestedNode2['default'], (0, _extends3['default'])({}, props, {
    nodeType: 'Iterable',
    nodeTypeIndicator: '()',
    createItemString: createItemString
  }));
}