'use strict';

Object.defineProperty(exports, '__esModule', { value: true });

const core = require('./core-98ac7c28.js');
require('./_commonjsHelpers-aa459cfa.js');
const version = require('./version-0bca6e7a.js');
const index = require('./index-735f6eb2.js');

/**
 * Performs a deep comparison between two values to determine if they are
 * equivalent.
 *
 * **Note:** This method supports comparing arrays, array buffers, booleans,
 * date objects, error objects, maps, numbers, `Object` objects, regexes,
 * sets, strings, symbols, and typed arrays. `Object` objects are compared
 * by their own, not inherited, enumerable properties. Functions and DOM
 * nodes are compared by strict equality, i.e. `===`.
 *
 * @static
 * @memberOf _
 * @since 0.1.0
 * @category Lang
 * @param {*} value The value to compare.
 * @param {*} other The other value to compare.
 * @returns {boolean} Returns `true` if the values are equivalent, else `false`.
 * @example
 *
 * var object = { 'a': 1 };
 * var other = { 'a': 1 };
 *
 * _.isEqual(object, other);
 * // => true
 *
 * object === other;
 * // => false
 */
function isEqual(value, other) {
  return version.baseIsEqual(value, other);
}

var isEqual_1 = isEqual;

/* Built-in method references for those with the same name as other `lodash` methods. */
var nativeCeil = Math.ceil,
    nativeMax = Math.max;

/**
 * The base implementation of `_.range` and `_.rangeRight` which doesn't
 * coerce arguments.
 *
 * @private
 * @param {number} start The start of the range.
 * @param {number} end The end of the range.
 * @param {number} step The value to increment or decrement by.
 * @param {boolean} [fromRight] Specify iterating from right to left.
 * @returns {Array} Returns the range of numbers.
 */
function baseRange(start, end, step, fromRight) {
  var index = -1,
      length = nativeMax(nativeCeil((end - start) / (step || 1)), 0),
      result = Array(length);

  while (length--) {
    result[fromRight ? length : ++index] = start;
    start += step;
  }
  return result;
}

var _baseRange = baseRange;

/**
 * Checks if the given arguments are from an iteratee call.
 *
 * @private
 * @param {*} value The potential iteratee value argument.
 * @param {*} index The potential iteratee index or key argument.
 * @param {*} object The potential iteratee object argument.
 * @returns {boolean} Returns `true` if the arguments are from an iteratee call,
 *  else `false`.
 */
function isIterateeCall(value, index, object) {
  if (!version.isObject(object)) {
    return false;
  }
  var type = typeof index;
  if (type == 'number'
        ? (version.isArrayLike(object) && version.isIndex(index, object.length))
        : (type == 'string' && index in object)
      ) {
    return version.eq(object[index], value);
  }
  return false;
}

var _isIterateeCall = isIterateeCall;

/** Used as references for various `Number` constants. */
var NAN = 0 / 0;

/** Used to match leading and trailing whitespace. */
var reTrim = /^\s+|\s+$/g;

/** Used to detect bad signed hexadecimal string values. */
var reIsBadHex = /^[-+]0x[0-9a-f]+$/i;

/** Used to detect binary string values. */
var reIsBinary = /^0b[01]+$/i;

/** Used to detect octal string values. */
var reIsOctal = /^0o[0-7]+$/i;

/** Built-in method references without a dependency on `root`. */
var freeParseInt = parseInt;

/**
 * Converts `value` to a number.
 *
 * @static
 * @memberOf _
 * @since 4.0.0
 * @category Lang
 * @param {*} value The value to process.
 * @returns {number} Returns the number.
 * @example
 *
 * _.toNumber(3.2);
 * // => 3.2
 *
 * _.toNumber(Number.MIN_VALUE);
 * // => 5e-324
 *
 * _.toNumber(Infinity);
 * // => Infinity
 *
 * _.toNumber('3.2');
 * // => 3.2
 */
function toNumber(value) {
  if (typeof value == 'number') {
    return value;
  }
  if (version.isSymbol(value)) {
    return NAN;
  }
  if (version.isObject(value)) {
    var other = typeof value.valueOf == 'function' ? value.valueOf() : value;
    value = version.isObject(other) ? (other + '') : other;
  }
  if (typeof value != 'string') {
    return value === 0 ? value : +value;
  }
  value = value.replace(reTrim, '');
  var isBinary = reIsBinary.test(value);
  return (isBinary || reIsOctal.test(value))
    ? freeParseInt(value.slice(2), isBinary ? 2 : 8)
    : (reIsBadHex.test(value) ? NAN : +value);
}

var toNumber_1 = toNumber;

/** Used as references for various `Number` constants. */
var INFINITY = 1 / 0,
    MAX_INTEGER = 1.7976931348623157e+308;

/**
 * Converts `value` to a finite number.
 *
 * @static
 * @memberOf _
 * @since 4.12.0
 * @category Lang
 * @param {*} value The value to convert.
 * @returns {number} Returns the converted number.
 * @example
 *
 * _.toFinite(3.2);
 * // => 3.2
 *
 * _.toFinite(Number.MIN_VALUE);
 * // => 5e-324
 *
 * _.toFinite(Infinity);
 * // => 1.7976931348623157e+308
 *
 * _.toFinite('3.2');
 * // => 3.2
 */
function toFinite(value) {
  if (!value) {
    return value === 0 ? value : 0;
  }
  value = toNumber_1(value);
  if (value === INFINITY || value === -INFINITY) {
    var sign = (value < 0 ? -1 : 1);
    return sign * MAX_INTEGER;
  }
  return value === value ? value : 0;
}

var toFinite_1 = toFinite;

/**
 * Creates a `_.range` or `_.rangeRight` function.
 *
 * @private
 * @param {boolean} [fromRight] Specify iterating from right to left.
 * @returns {Function} Returns the new range function.
 */
function createRange(fromRight) {
  return function(start, end, step) {
    if (step && typeof step != 'number' && _isIterateeCall(start, end, step)) {
      end = step = undefined;
    }
    // Ensure the sign of `-0` is preserved.
    start = toFinite_1(start);
    if (end === undefined) {
      end = start;
      start = 0;
    } else {
      end = toFinite_1(end);
    }
    step = step === undefined ? (start < end ? 1 : -1) : toFinite_1(step);
    return _baseRange(start, end, step, fromRight);
  };
}

var _createRange = createRange;

/**
 * Creates an array of numbers (positive and/or negative) progressing from
 * `start` up to, but not including, `end`. A step of `-1` is used if a negative
 * `start` is specified without an `end` or `step`. If `end` is not specified,
 * it's set to `start` with `start` then set to `0`.
 *
 * **Note:** JavaScript follows the IEEE-754 standard for resolving
 * floating-point values which can produce unexpected results.
 *
 * @static
 * @since 0.1.0
 * @memberOf _
 * @category Util
 * @param {number} [start=0] The start of the range.
 * @param {number} end The end of the range.
 * @param {number} [step=1] The value to increment or decrement by.
 * @returns {Array} Returns the range of numbers.
 * @see _.inRange, _.rangeRight
 * @example
 *
 * _.range(4);
 * // => [0, 1, 2, 3]
 *
 * _.range(-4);
 * // => [0, -1, -2, -3]
 *
 * _.range(1, 5);
 * // => [1, 2, 3, 4]
 *
 * _.range(0, 20, 5);
 * // => [0, 5, 10, 15]
 *
 * _.range(0, -4, -1);
 * // => [0, -1, -2, -3]
 *
 * _.range(1, 4, 0);
 * // => [1, 1, 1]
 *
 * _.range(0);
 * // => []
 */
var range = _createRange();

var range_1 = range;

class DataURLImageSupport {
    insert(file, done, progressFn) {
        const reader = new FileReader();
        reader.onload = () => {
            const dataURL = reader.result;
            setTimeout(() => {
                done(null, dataURL.toString());
            }, 2000);
        };
        let progress = 0;
        progressFn(progress, 0, 100);
        range_1(1, 100).forEach(n => {
            setTimeout(() => {
                progressFn(n, n, 100);
            }, n * 20);
        });
        // if external asset support .. add reader.readAsArrayBuffer ..
        reader.readAsDataURL(file);
    }
    delete(src, done) {
        done();
    }
}

const Author = class {
    constructor(hostRef) {
        core.registerInstance(this, hostRef);
        this._modelLoadedState = false;
        /**
         * Allows disabling of the default behaviour which is to look up and load the JS bundle that define the Custom Elements
         * used by the item config.
         * This if for advanced use cases when using the pie-player in a container that is managing loading of Custom Elements and Controllers.
         */
        this.disableBundler = false;
        /**
         * Adds a preview view which will render the content in another tab as it may appear to a student or instructor.
         */
        this.addPreview = false;
        this.elementsLoaded = false;
        this.pieLoader = new version.PieLoader();
        this.fileInput = null;
        this.imageHandler = null;
        /** external providers can set this if they need to upload the assets to the cloud etc. by default we use data urls */
        this.imageSupport = new DataURLImageSupport();
        this.version = version.VERSION;
        this.handleFileInputChange = (e) => {
            const input = e.target;
            if (!this.imageHandler) {
                console.error("no image handler - but file input change triggered?");
                return;
            }
            const files = input.files;
            if (files.length < 1 || !files[0]) {
                this.imageHandler.cancel();
                this.imageHandler = null;
            }
            else {
                const file = files[0];
                this.imageHandler.fileChosen(file);
                this.fileInput.value = "";
                this.imageSupport.insert(file, (e, src) => {
                    if (e) {
                        console.warn("error inserting image: ", e.message);
                        console.error(e);
                    }
                    this.imageHandler.done(e, src);
                    this.imageHandler = null;
                }, (percent, bytes, total) => this.imageHandler.progress(percent, bytes, total));
            }
        };
        this.handleInsertImage = (e) => {
            console.log("[handleInsertImage]", e);
            this.imageHandler = e.detail;
            this.fileInput.click();
        };
        this.handleDeleteImage = (e) => {
            console.log("[handleDeleteImage ..]", e);
            this.imageSupport.delete(e.detail.src, e.detail.done);
        };
        this.modelUpdated = core.createEvent(this, "modelUpdated", 7);
        this.modelLoaded = core.createEvent(this, "modelLoaded", 7);
        this.doc = core.getContext(this, "document");
    }
    getRenderMarkup() {
        let markup = this.pieContentModel ? this.pieContentModel.markup : "";
        if (markup) {
            Object.keys(this.pieContentModel.elements).forEach(key => {
                markup = markup.split(key).join(key + "-config");
            });
            return markup;
        }
    }
    async watchConfig(newValue, oldValue) {
        if (newValue && !isEqual_1(newValue, oldValue)) {
            try {
                this.elementsLoaded = false;
                this._modelLoadedState = false;
                this.pieContentModel = version.pieContentFromConfig(newValue);
                this.addConfigTags(this.pieContentModel);
                this.loadPieElements();
            }
            catch (error) {
                console.log(`ERROR ${error}`);
            }
        }
    }
    addConfigTags(c) {
        if (!c.markup && c.models) {
            const tags = c.models.map(model => {
                return `<${model.element}-config id="${model.id}"></${model.element}-config>`;
            });
            c.markup = tags.join("");
        }
    }
    async watchElementsLoaded(newValue, oldValue) {
        if (newValue && !oldValue) {
            await this.updateModels();
        }
    }
    async updateModels() {
        if (this.pieContentModel &&
            this.pieContentModel.elements &&
            this.pieContentModel.markup) {
            if (!this.pieContentModel.models) {
                this.pieContentModel.models = [];
            }
            const tempDiv = this.doc.createElement("div");
            tempDiv.innerHTML = this.pieContentModel.markup;
            const elsWithId = tempDiv.querySelectorAll("[id]");
            // set up a model for each pie defined in the markup
            elsWithId.forEach(el => {
                const pieElName = el.tagName.toLowerCase().split("-config")[0];
                // initialize emtpy model if this is a pie
                if (this.pieContentModel.elements[pieElName]) {
                    const elementId = el.getAttribute("id");
                    if (!this.pieContentModel.models.find(m => m.id === elementId)) {
                        const model = { id: elementId, element: pieElName };
                        this.pieContentModel.models.push(model);
                    }
                }
            });
            tempDiv.remove();
        }
        if (this.pieContentModel && this.pieContentModel.models) {
            this.pieContentModel.models.map(model => {
                let pieEl = this.el.querySelector(`[id='${model.id}']`);
                !pieEl && (pieEl = this.el.querySelector(`[pie-id='${model.id}']`));
                if (pieEl) {
                    const pieElName = pieEl.tagName.toLowerCase().split("-config")[0];
                    const packageName = version.parseNpm(this.pieContentModel.elements[pieElName])
                        .name;
                    pieEl.model = model;
                    if (this.configSettings && this.configSettings[packageName]) {
                        pieEl.configuration = this.configSettings[packageName];
                    }
                }
            });
            if (this._modelLoadedState === false) {
                this._modelLoadedState = true;
                this.modelLoaded.emit(this.pieContentModel);
            }
        }
    }
    componentDidUnload() {
        this.el.removeEventListener(index.lib_3.TYPE, this.handleInsertImage);
        this.el.removeEventListener(index.lib_2.TYPE, this.handleDeleteImage);
        this.fileInput.removeEventListener("change", this.handleFileInputChange);
    }
    async componentWillLoad() {
        if (this.config) {
            this.watchConfig(this.config, {});
        }
        // Note: cannot use the @Listen decorator as creates bundling problems due
        // to `.` in event name.
        this.el.addEventListener(index.lib_1.TYPE, (e) => {
            // set the internal model
            // emit a content-item level event with the model
            if (this.pieContentModel && e.update) {
                this.pieContentModel.models.forEach(m => {
                    if (m.id === e.update.id && m.name === e.update.name) {
                        Object.assign(m, e.update);
                    }
                });
            }
            if (this._modelLoadedState) {
                this.modelUpdated.emit(this.pieContentModel);
            }
        });
        this.el.addEventListener(index.lib_3.TYPE, this.handleInsertImage);
        this.el.addEventListener(index.lib_2.TYPE, this.handleDeleteImage);
    }
    async componentDidLoad() {
        await this.afterRender();
    }
    async componentDidUpdate() {
        await this.afterRender();
        if (this.fileInput) {
            this.fileInput.addEventListener("change", this.handleFileInputChange);
        }
    }
    async loadPieElements() {
        if (this.config && !this.disableBundler) {
            let endpoints = version.DEFAULT_ENDPOINTS.stage;
            if (this.bundleHost &&
                ["dev", "stage", "prod"].includes(this.bundleHost)) {
                endpoints = version.DEFAULT_ENDPOINTS[this.bundleHost];
            }
            if (this.bundleEndpoints) {
                endpoints = this.bundleEndpoints;
            }
            await this.pieLoader.loadCloudPies({
                content: this.pieContentModel,
                doc: this.doc,
                endpoints,
                useCdn: false
            });
        }
    }
    renderMath() {
        setTimeout(() => {
            version.mr.renderMath(this.el);
        }, 50);
    }
    async afterRender() {
        if (this.pieContentModel &&
            this.pieContentModel.markup &&
            !this.elementsLoaded) {
            const elements = Object.keys(this.pieContentModel.elements).map(el => ({
                name: el,
                tag: `${el}-config`
            }));
            const loadedInfo = await this.pieLoader.elementsHaveLoaded(elements);
            if (loadedInfo.val &&
                !!loadedInfo.elements.find(el => this.pieContentModel.elements[el.name])) {
                this.elementsLoaded = true;
                this.renderMath();
            }
        }
    }
    /**
     * Utility method to add a `@pie-element/rubric` section to an item config when creating an item should be used before setting the config.
     *
     * @deprecated this method is for temporary use, will be removed at next major release
     *
     * @param config the item config to mutate
     * @param rubricModel
     */
    async addRubricToConfig(config, rubricModel) {
        if (!rubricModel) {
            rubricModel = {
                id: "rubric",
                element: "pie-rubric",
                points: ["", "", "", ""],
                maxPoints: 4,
                excludeZero: false
            };
        }
        const configPieContent = version.pieContentFromConfig(config);
        version.addPackageToContent(configPieContent, "@pie-element/rubric", rubricModel);
        return version.addRubric(configPieContent);
    }
    render() {
        if (this.pieContentModel && this.pieContentModel.markup) {
            const markup = this.getRenderMarkup();
            if (this.addPreview) {
                return (core.h("pie-preview-layout", { config: this.config }, core.h("div", { slot: "configure" }, core.h("pie-spinner", { active: !this.elementsLoaded }, core.h("div", { innerHTML: markup }))), core.h("input", { type: "file", hidden: true, ref: r => (this.fileInput = r) })));
            }
            else {
                return (core.h("pie-spinner", { active: !this.elementsLoaded }, core.h("div", { innerHTML: markup }), core.h("input", { type: "file", hidden: true, ref: r => (this.fileInput = r) })));
            }
        }
        else {
            return core.h("pie-spinner", null);
        }
    }
    get el() { return core.getElement(this); }
    static get watchers() { return {
        "config": ["watchConfig"],
        "elementsLoaded": ["watchElementsLoaded"]
    }; }
    static get style() { return ".pie-loading{position:absolute;height:100%;width:100%;background-color:#fff;bottom:0;left:0;right:0;top:0;z-index:9999;opacity:.4}.pie-loading.fixed{position:fixed}.pie-loading:before{-webkit-box-shadow:0 0 35px rgba(63,81,181,.9);box-shadow:0 0 35px rgba(63,81,181,.9);width:50px;height:50px;-moz-animation:spinPulse 1s infinite ease-in-out;-webkit-animation:spinPulse 1s linear infinite;margin:-25px 0 0 -25px}.pie-loading:after,.pie-loading:before{content:\"\";background-color:transparent;border:5px solid rgba(63,81,181,.9);opacity:.9;border-right:5px solid transparent;border-left:5px solid transparent;border-radius:50px;position:absolute;top:50%;left:50%}.pie-loading:after{-webkit-box-shadow:0 0 15px rgba(63,81,181,.9);box-shadow:0 0 15px rgba(63,81,181,.9);width:30px;height:30px;-moz-animation:spinoffPulse 1s infinite linear;-webkit-animation:spinoffPulse 1s linear infinite;margin:-15px 0 0 -15px}\@-webkit-keyframes spinPulse{0%{-webkit-transform:rotate(160deg);transform:rotate(160deg);opacity:0;-webkit-box-shadow:0 0 1px rgba(63,81,181,.9);box-shadow:0 0 1px rgba(63,81,181,.9)}50%{-webkit-transform:rotate(145deg);transform:rotate(145deg);opacity:1}to{-webkit-transform:rotate(-320deg);transform:rotate(-320deg);opacity:0}}\@keyframes spinPulse{0%{-webkit-transform:rotate(160deg);transform:rotate(160deg);opacity:0;-webkit-box-shadow:0 0 1px rgba(63,81,181,.9);box-shadow:0 0 1px rgba(63,81,181,.9)}50%{-webkit-transform:rotate(145deg);transform:rotate(145deg);opacity:1}to{-webkit-transform:rotate(-320deg);transform:rotate(-320deg);opacity:0}}\@-webkit-keyframes spinoffPulse{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}to{-webkit-transform:rotate(1turn);transform:rotate(1turn)}}\@keyframes spinoffPulse{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}to{-webkit-transform:rotate(1turn);transform:rotate(1turn)}}"; }
};

exports.pie_author = Author;
