import { h } from "@stencil/core";
import * as mr from "@pie-lib/math-rendering";
import { PieLoader, DEFAULT_ENDPOINTS } from "../../pie-loader";
import { pieContentFromConfig } from "../../utils/utils";
import parseNpm from "parse-package-name";
import _isEqual from "lodash/isEqual";
import { addPackageToContent, addRubric } from "../../rubric-utils";
import { ModelUpdatedEvent, InsertImageEvent, DeleteImageEvent } from "@pie-framework/pie-configure-events";
import { DataURLImageSupport } from "./dataurl-image-support";
import { VERSION } from "../../version";
/**
 * Pie Author will load a Pie Content model for authoring.
 * It needs to be run in the context
 */
export class Author {
    constructor() {
        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 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;
        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);
        };
    }
    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(newValue, oldValue)) {
            try {
                this.elementsLoaded = false;
                this._modelLoadedState = false;
                this.pieContentModel = 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 = 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(InsertImageEvent.TYPE, this.handleInsertImage);
        this.el.removeEventListener(DeleteImageEvent.TYPE, this.handleDeleteImage);
        if (this.fileInput) {
            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(ModelUpdatedEvent.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.element === e.update.element) {
                        Object.assign(m, e.update);
                    }
                });
            }
            if (this._modelLoadedState) {
                this.modelUpdated.emit(this.pieContentModel);
            }
        });
        this.el.addEventListener(InsertImageEvent.TYPE, this.handleInsertImage);
        this.el.addEventListener(DeleteImageEvent.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 = DEFAULT_ENDPOINTS.prod;
            if (this.bundleHost &&
                ["dev", "stage", "prod"].includes(this.bundleHost)) {
                endpoints = 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(() => {
            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 = pieContentFromConfig(config);
        addPackageToContent(configPieContent, "@pie-element/rubric", rubricModel);
        return addRubric(configPieContent);
    }
    render() {
        if (this.pieContentModel && this.pieContentModel.markup) {
            const markup = this.getRenderMarkup();
            if (this.addPreview) {
                return (h("pie-preview-layout", { config: this.config },
                    h("div", { slot: "configure" },
                        h("pie-spinner", { active: !this.elementsLoaded },
                            h("div", { innerHTML: markup }))),
                    h("input", { type: "file", hidden: true, ref: r => (this.fileInput = r) })));
            }
            else {
                return (h("pie-spinner", { active: !this.elementsLoaded },
                    h("div", { innerHTML: markup }),
                    h("input", { type: "file", hidden: true, ref: r => (this.fileInput = r) })));
            }
        }
        else {
            return h("pie-spinner", null);
        }
    }
    static get is() { return "pie-author"; }
    static get originalStyleUrls() { return {
        "$": ["../components.css"]
    }; }
    static get styleUrls() { return {
        "$": ["../components.css"]
    }; }
    static get properties() { return {
        "bundleHost": {
            "type": "string",
            "mutable": false,
            "complexType": {
                "original": "string",
                "resolved": "string",
                "references": {}
            },
            "required": false,
            "optional": true,
            "docs": {
                "tags": [],
                "text": "Optionally specifies the back-end that builds and hosts javascript bundles for rendering assessment items.\nThis property lets you choose which environment to use, from 'dev' , 'stage' or 'prod' environments.\nUntil 1.0 will default to 'stage'."
            },
            "attribute": "bundle-host",
            "reflect": false
        },
        "bundleEndpoints": {
            "type": "unknown",
            "mutable": false,
            "complexType": {
                "original": "BundleEndpoints",
                "resolved": "BundleEndpoints",
                "references": {
                    "BundleEndpoints": {
                        "location": "import",
                        "path": "../../pie-loader"
                    }
                }
            },
            "required": false,
            "optional": true,
            "docs": {
                "tags": [],
                "text": "Provide this property override the default endpoints used by the player to retrieve JS\nbundles. Must be set before setting the config property.\nMost users will not need to use this property."
            }
        },
        "disableBundler": {
            "type": "boolean",
            "mutable": false,
            "complexType": {
                "original": "boolean",
                "resolved": "boolean",
                "references": {}
            },
            "required": false,
            "optional": false,
            "docs": {
                "tags": [],
                "text": "Allows disabling of the default behaviour which is to look up and load the JS bundle that define the Custom Elements\nused by the item config.\nThis if for advanced use cases when using the pie-player in a container that is managing loading of Custom Elements and Controllers."
            },
            "attribute": "disable-bundler",
            "reflect": false,
            "defaultValue": "false"
        },
        "addRubric": {
            "type": "boolean",
            "mutable": false,
            "complexType": {
                "original": "boolean",
                "resolved": "boolean",
                "references": {}
            },
            "required": false,
            "optional": false,
            "docs": {
                "tags": [],
                "text": "If set the player will add a rubric authoring interaction to the config"
            },
            "attribute": "add-rubric",
            "reflect": false
        },
        "addPreview": {
            "type": "boolean",
            "mutable": false,
            "complexType": {
                "original": "boolean",
                "resolved": "boolean",
                "references": {}
            },
            "required": false,
            "optional": false,
            "docs": {
                "tags": [],
                "text": "Adds a preview view which will render the content in another tab as it may appear to a student or instructor."
            },
            "attribute": "add-preview",
            "reflect": false,
            "defaultValue": "false"
        },
        "config": {
            "type": "unknown",
            "mutable": false,
            "complexType": {
                "original": "ItemConfig",
                "resolved": "AdvancedItemConfig | PieContent",
                "references": {
                    "ItemConfig": {
                        "location": "import",
                        "path": "../../interface"
                    }
                }
            },
            "required": false,
            "optional": false,
            "docs": {
                "tags": [],
                "text": "The Pie config model."
            }
        },
        "configSettings": {
            "type": "unknown",
            "mutable": false,
            "complexType": {
                "original": "{ [packageName: string]: Object }",
                "resolved": "{ [packageName: string]: Object; }",
                "references": {
                    "Object": {
                        "location": "global"
                    }
                }
            },
            "required": false,
            "optional": true,
            "docs": {
                "tags": [],
                "text": "To customize the standard behaviour provided by interaction configuration views you can\nprovide settings key-ed by the package name.  e.g.\n\n`{ '@pie-element/inline-choice': { promptLabel: 'Item Stem' } }`\n\nThe settings that are configurable for each authoring view are documented in\nthe `@package-name/docs` folder for each package."
            }
        },
        "imageSupport": {
            "type": "unknown",
            "mutable": false,
            "complexType": {
                "original": "ExternalImageSupport",
                "resolved": "ExternalImageSupport",
                "references": {
                    "ExternalImageSupport": {
                        "location": "import",
                        "path": "./dataurl-image-support"
                    }
                }
            },
            "required": false,
            "optional": false,
            "docs": {
                "tags": [],
                "text": "external providers can set this if they need to upload the assets to the cloud etc. by default we use data urls"
            },
            "defaultValue": "new DataURLImageSupport()"
        },
        "version": {
            "type": "string",
            "mutable": false,
            "complexType": {
                "original": "string",
                "resolved": "string",
                "references": {}
            },
            "required": false,
            "optional": false,
            "docs": {
                "tags": [],
                "text": ""
            },
            "attribute": "version",
            "reflect": false,
            "defaultValue": "VERSION"
        }
    }; }
    static get contextProps() { return [{
            "name": "doc",
            "context": "document"
        }]; }
    static get states() { return {
        "elementsLoaded": {},
        "fileInput": {}
    }; }
    static get events() { return [{
            "method": "modelUpdated",
            "name": "modelUpdated",
            "bubbles": true,
            "cancelable": true,
            "composed": true,
            "docs": {
                "tags": [],
                "text": "Emmitted when the model for the content has been updated within the ui due to user action."
            },
            "complexType": {
                "original": "any",
                "resolved": "any",
                "references": {}
            }
        }, {
            "method": "modelLoaded",
            "name": "modelLoaded",
            "bubbles": true,
            "cancelable": true,
            "composed": true,
            "docs": {
                "tags": [],
                "text": "Emmitted when the content models in the config have been set on the content"
            },
            "complexType": {
                "original": "any",
                "resolved": "any",
                "references": {}
            }
        }]; }
    static get methods() { return {
        "addRubricToConfig": {
            "complexType": {
                "signature": "(config: ItemConfig, rubricModel?: any) => Promise<PieContent>",
                "parameters": [{
                        "tags": [{
                                "text": "config the item config to mutate",
                                "name": "param"
                            }],
                        "text": "the item config to mutate"
                    }, {
                        "tags": [{
                                "text": "rubricModel",
                                "name": "param"
                            }],
                        "text": ""
                    }],
                "references": {
                    "Promise": {
                        "location": "global"
                    },
                    "PieContent": {
                        "location": "import",
                        "path": "../../interface"
                    },
                    "ItemConfig": {
                        "location": "import",
                        "path": "../../interface"
                    },
                    "PieModel": {
                        "location": "import",
                        "path": "../../interface"
                    }
                },
                "return": "Promise<PieContent>"
            },
            "docs": {
                "text": "Utility method to add a `@pie-element/rubric` section to an item config when creating an item should be used before setting the config.",
                "tags": [{
                        "name": "deprecated",
                        "text": "this method is for temporary use, will be removed at next major release"
                    }, {
                        "name": "param",
                        "text": "config the item config to mutate"
                    }, {
                        "name": "param",
                        "text": "rubricModel"
                    }]
            }
        }
    }; }
    static get elementRef() { return "el"; }
    static get watchers() { return [{
            "propName": "config",
            "methodName": "watchConfig"
        }, {
            "propName": "elementsLoaded",
            "methodName": "watchElementsLoaded"
        }]; }
}
