text: import './polyfill';
  import Delta from 'quill-delta';
  import Editor from './editor';
  import Emitter from './emitter';
  import Module from './module';
  import Parchment from 'parchment';
  import Selection, { Range } from './selection';
  import extend from 'extend';
  import logger from './logger';
  import Theme from './theme';
  
  let debug = logger('quill');
  
  
  class Quill {
    static debug(limit) {
      if (limit === true) {
        limit = 'log';
      }
      logger.level(limit);
    }
  
    static find(node) {
      return node.__quill || Parchment.find(node);
    }
  
    static import(name) {
      if (this.imports[name] == null) {
        debug.error(`Cannot import ${name}. Are you sure it was registered?`);
      }
      return this.imports[name];
    }
  
    static register(path, target, overwrite = false) {
      if (typeof path !== 'string') {
        let name = path.attrName || path.blotName;
        if (typeof name === 'string') {
          // register(Blot | Attributor, overwrite)
          this.register('formats/' + name, path, target);
        } else {
          Object.keys(path).forEach((key) => {
            this.register(key, path[key], target);
          });
        }
      } else {
        if (this.imports[path] != null && !overwrite) {
          debug.warn(`Overwriting ${path} with`, target);
        }
        this.imports[path] = target;
        if ((path.startsWith('blots/') || path.startsWith('formats/')) &&
            target.blotName !== 'abstract') {
          Parchment.register(target);
        } else if (path.startsWith('modules') && typeof target.register === 'function') {
          target.register();
        }
      }
    }
  
    constructor(container, options = {}) {
      this.options = expandConfig(container, options);
      this.container = this.options.container;
      if (this.container == null) {
        return debug.error('Invalid Quill container', container);
      }
      if (this.options.debug) {
        Quill.debug(this.options.debug);
      }
      let html = this.container.innerHTML.trim();
      this.container.classList.add('ql-container');
      this.container.innerHTML = '';
      this.container.__quill = this;
      this.root = this.addContainer('ql-editor');
      this.root.classList.add('ql-blank');
      this.root.setAttribute('data-gramm', false);
      this.scrollingContainer = this.options.scrollingContainer || this.root;
      this.emitter = new Emitter();
      this.scroll = Parchment.create(this.root, {
        emitter: this.emitter,
        scrollingContainer: this.scrollingContainer,
        whitelist: this.options.formats
      });
      this.editor = new Editor(this.scroll);
      this.selection = new Selection(this.scroll, this.emitter);
      this.theme = new this.options.theme(this, this.options);
      this.keyboard = this.theme.addModule('keyboard');
      this.clipboard = this.theme.addModule('clipboard');
      this.history = this.theme.addModule('history');
      this.theme.init();
      this.emitter.on(Emitter.events.EDITOR_CHANGE, (type) => {
        if (type === Emitter.events.TEXT_CHANGE) {
          this.root.classList.toggle('ql-blank', this.editor.isBlank());
        }
      });
      this.emitter.on(Emitter.events.SCROLL_UPDATE, (source, mutations) => {
        let range = this.selection.lastRange;
        let index = range && range.length === 0 ? range.index : undefined;
        modify.call(this, () => {
          return this.editor.update(null, mutations, index);
        }, source);
      });
      let contents = this.clipboard.convert(`<div class='ql-editor' style="white-space: normal;">${html}<p><br></p></div>`);
      this.setContents(contents);
      this.history.clear();
      if (this.options.placeholder) {
        this.root.setAttribute('data-placeholder', this.options.placeholder);
      }
      if (this.options.readOnly) {
        this.disable();
      }
    }
  
    addContainer(container, refNode = null) {
      if (typeof container === 'string') {
        let className = container;
        container = document.createElement('div');
        container.classList.add(className);
      }
      this.container.insertBefore(container, refNode);
      return container;
    }
  
    blur() {
      this.selection.setRange(null);
    }
  
    deleteText(index, length, source) {
      [index, length, , source] = overload(index, length, source);
      return modify.call(this, () => {
        return this.editor.deleteText(index, length);
      }, source, index, -1*length);
    }
  
    disable() {
      this.enable(false);
    }
  
    enable(enabled = true) {
      this.scroll.enable(enabled);
      this.container.classList.toggle('ql-disabled', !enabled);
    }
  
    focus() {
      let scrollTop = this.scrollingContainer.scrollTop;
      this.selection.focus();
      this.scrollingContainer.scrollTop = scrollTop;
      this.selection.scrollIntoView();
    }
  
    format(name, value, source = Emitter.sources.API) {
      return modify.call(this, () => {
        let range = this.getSelection(true);
        let change = new Delta();
        if (range == null) {
          return change;
        } else if (Parchment.query(name, Parchment.Scope.BLOCK)) {
          change = this.editor.formatLine(range.index, range.length, { [name]: value });
        } else if (range.length === 0) {
          this.selection.format(name, value);
          return change;
        } else {
          change = this.editor.formatText(range.index, range.length, { [name]: value });
        }
        this.setSelection(range, Emitter.sources.SILENT);
        return change;
      }, source);
    }
  
    formatLine(index, length, name, value, source) {
      let formats;
      [index, length, formats, source] = overload(index, length, name, value, source);
      return modify.call(this, () => {
        return this.editor.formatLine(index, length, formats);
      }, source, index, 0);
    }
  
    formatText(index, length, name, value, source) {
      let formats;
      [index, length, formats, source] = overload(index, length, name, value, source);
      return modify.call(this, () => {
        return this.editor.formatText(index, length, formats);
      }, source, index, 0);
    }
  
    getBounds(index, length = 0) {
      let bounds;
      if (typeof index === 'number') {
        bounds = this.selection.getBounds(index, length);
      } else {
        bounds = this.selection.getBounds(index.index, index.length);
      }
      let containerBounds = this.container.getBoundingClientRect();
      return {
        bottom: bounds.bottom - containerBounds.top,
        height: bounds.height,
        left: bounds.left - containerBounds.left,
        right: bounds.right - containerBounds.left,
        top: bounds.top - containerBounds.top,
        width: bounds.width
      };
    }
  
    getContents(index = 0, length = this.getLength() - index) {
      [index, length] = overload(index, length);
      return this.editor.getContents(index, length);
    }
  
    getFormat(index = this.getSelection(), length = 0) {
      if (typeof index === 'number') {
        return this.editor.getFormat(index, length);
      } else {
        return this.editor.getFormat(index.index, index.length);
      }
    }
  
    getIndex(blot) {
      return blot.offset(this.scroll);
    }
  
    getLength() {
      return this.scroll.length();
    }
  
    getLeaf(index) {
      return this.scroll.leaf(index);
    }
  
    getLine(index) {
      return this.scroll.line(index);
    }
  
    getLines(index = 0, length = Number.MAX_VALUE) {
      if (typeof index !== 'number') {
        return this.scroll.lines(index.index, index.length);
      } else {
        return this.scroll.lines(index, length);
      }
    }
  
    getModule(name) {
      return this.theme.modules[name];
    }
  
    getSelection(focus = false) {
      if (focus) this.focus();
      this.update();  // Make sure we access getRange with editor in consistent state
      return this.selection.getRange()[0];
    }
  
    getText(index = 0, length = this.getLength() - index) {
      [index, length] = overload(index, length);
      return this.editor.getText(index, length);
    }
  
    hasFocus() {
      return this.selection.hasFocus();
    }
  
    insertEmbed(index, embed, value, source = Quill.sources.API) {
      return modify.call(this, () => {
        return this.editor.insertEmbed(index, embed, value);
      }, source, index);
    }
  
    insertText(index, text, name, value, source) {
      let formats;
      [index, , formats, source] = overload(index, 0, name, value, source);
      return modify.call(this, () => {
        return this.editor.insertText(index, text, formats);
      }, source, index, text.length);
    }
  
    isEnabled() {
      return !this.container.classList.contains('ql-disabled');
    }
  
    off() {
      return this.emitter.off.apply(this.emitter, arguments);
    }
  
    on() {
      return this.emitter.on.apply(this.emitter, arguments);
    }
  
    once() {
      return this.emitter.once.apply(this.emitter, arguments);
    }
  
    pasteHTML(index, html, source) {
      this.clipboard.dangerouslyPasteHTML(index, html, source);
    }
  
    removeFormat(index, length, source) {
      [index, length, , source] = overload(index, length, source);
      return modify.call(this, () => {
        return this.editor.removeFormat(index, length);
      }, source, index);
    }
  
    setContents(delta, source = Emitter.sources.API) {
      return modify.call(this, () => {
        delta = new Delta(delta);
        let length = this.getLength();
        let deleted = this.editor.deleteText(0, length);
        let applied = this.editor.applyDelta(delta);
        let lastOp = applied.ops[applied.ops.length - 1];
        if (lastOp != null && typeof(lastOp.insert) === 'string' && lastOp.insert[lastOp.insert.length-1] === '\n') {
          this.editor.deleteText(this.getLength() - 1, 1);
          applied.delete(1);
        }
        let ret = deleted.compose(applied);
        return ret;
      }, source);
    }
  
    setSelection(index, length, source) {
      if (index == null) {
        this.selection.setRange(null, length || Quill.sources.API);
      } else {
        [index, length, , source] = overload(index, length, source);
        this.selection.setRange(new Range(index, length), source);
      }
      if (source !== Emitter.sources.SILENT) {
        this.selection.scrollIntoView();
      }
    }
  
    setText(text, source = Emitter.sources.API) {
      let delta = new Delta().insert(text);
      return this.setContents(delta, source);
    }
  
    update(source = Emitter.sources.USER) {
      let change = this.scroll.update(source);   // Will update selection before selection.update() does if text changes
      this.selection.update(source);
      return change;
    }
  
    updateContents(delta, source = Emitter.sources.API) {
      return modify.call(this, () => {
        delta = new Delta(delta);
        return this.editor.applyDelta(delta, source);
      }, source, true);
    }
  }
  Quill.DEFAULTS = {
    bounds: null,
    formats: null,
    modules: {},
    placeholder: '',
    readOnly: false,
    scrollingContainer: null,
    strict: true,
    theme: 'default'
  };
  Quill.events = Emitter.events;
  Quill.sources = Emitter.sources;
  // eslint-disable-next-line no-undef
  Quill.version = typeof(QUILL_VERSION) === 'undefined' ? 'dev' : QUILL_VERSION;
  
  Quill.imports = {
    'delta'       : Delta,
    'parchment'   : Parchment,
    'core/module' : Module,
    'core/theme'  : Theme
  };
  
  
  function expandConfig(container, userConfig) {
    userConfig = extend(true, {
      container: container,
      modules: {
        clipboard: true,
        keyboard: true,
        history: true
      }
    }, userConfig);
    if (!userConfig.theme || userConfig.theme === Quill.DEFAULTS.theme) {
      userConfig.theme = Theme;
    } else {
      userConfig.theme = Quill.import(`themes/${userConfig.theme}`);
      if (userConfig.theme == null) {
        throw new Error(`Invalid theme ${userConfig.theme}. Did you register it?`);
      }
    }
    let themeConfig = extend(true, {}, userConfig.theme.DEFAULTS);
    [themeConfig, userConfig].forEach(function(config) {
      config.modules = config.modules || {};
      Object.keys(config.modules).forEach(function(module) {
        if (config.modules[module] === true) {
          config.modules[module] = {};
        }
      });
    });
    let moduleNames = Object.keys(themeConfig.modules).concat(Object.keys(userConfig.modules));
    let moduleConfig = moduleNames.reduce(function(config, name) {
      let moduleClass = Quill.import(`modules/${name}`);
      if (moduleClass == null) {
        debug.error(`Cannot load ${name} module. Are you sure you registered it?`);
      } else {
        config[name] = moduleClass.DEFAULTS || {};
      }
      return config;
    }, {});
    // Special case toolbar shorthand
    if (userConfig.modules != null && userConfig.modules.toolbar &&
        userConfig.modules.toolbar.constructor !== Object) {
      userConfig.modules.toolbar = {
        container: userConfig.modules.toolbar
      };
    }
    userConfig = extend(true, {}, Quill.DEFAULTS, { modules: moduleConfig }, themeConfig, userConfig);
    ['bounds', 'container', 'scrollingContainer'].forEach(function(key) {
      if (typeof userConfig[key] === 'string') {
        userConfig[key] = document.querySelector(userConfig[key]);
      }
    });
    userConfig.modules = Object.keys(userConfig.modules).reduce(function(config, name) {
      if (userConfig.modules[name]) {
        config[name] = userConfig.modules[name];
      }
      return config;
    }, {});
    return userConfig;
  }
  
  // Handle selection preservation and TEXT_CHANGE emission
  // common to modification APIs
  function modify(modifier, source, index, shift) {
    if (this.options.strict && !this.isEnabled() && source === Emitter.sources.USER) {
      return new Delta();
    }
    let range = index == null ? null : this.getSelection();
    let oldDelta = this.editor.delta;
    let change = modifier();
    if (range != null) {
      if (index === true) index = range.index;
      if (shift == null) {
        range = shiftRange(range, change, source);
      } else if (shift !== 0) {
        range = shiftRange(range, index, shift, source);
      }
      this.setSelection(range, Emitter.sources.SILENT);
    }
    if (change.length() > 0) {
      let args = [Emitter.events.TEXT_CHANGE, change, oldDelta, source];
      this.emitter.emit(Emitter.events.EDITOR_CHANGE, ...args);
      if (source !== Emitter.sources.SILENT) {
        this.emitter.emit(...args);
      }
    }
    return change;
  }
  
  function overload(index, length, name, value, source) {
    let formats = {};
    if (typeof index.index === 'number' && typeof index.length === 'number') {
      // Allow for throwaway end (used by insertText/insertEmbed)
      if (typeof length !== 'number') {
        source = value, value = name, name = length, length = index.length, index = index.index;
      } else {
        length = index.length, index = index.index;
      }
    } else if (typeof length !== 'number') {
      source = value, value = name, name = length, length = 0;
    }
    // Handle format being object, two format name/value strings or excluded
    if (typeof name === 'object') {
      formats = name;
      source = value;
    } else if (typeof name === 'string') {
      if (value != null) {
        formats[name] = value;
      } else {
        source = name;
      }
    }
    // Handle optional source
    source = source || Emitter.sources.API;
    return [index, length, formats, source];
  }
  
  function shiftRange(range, index, length, source) {
    if (range == null) return null;
    let start, end;
    if (index instanceof Delta) {
      [start, end] = [range.index, range.index + range.length].map(function(pos) {
        return index.transformPosition(pos, source !== Emitter.sources.USER);
      });
    } else {
      [start, end] = [range.index, range.index + range.length].map(function(pos) {
        if (pos < index || (pos === index && source === Emitter.sources.USER)) return pos;
        if (length >= 0) {
          return pos + length;
        } else {
          return Math.max(index, pos + length);
        }
      });
    }
    return new Range(start, end - start);
  }
  
  
  export { expandConfig, overload, Quill as default };
  