# constructors/js-template-component.js

    //@
    /**
     * @example
     *  Application.require('js-template-component')
     *      .then(function (
     *          // @type {JSTemplateComponent}
     *          JSTemplateComponentConstructor
     *      ) {
     *          new JSTemplateComponentConstructor(
     *              'my-custom-tagname',
     *              {
     *                  context: {
     *                      numberOfClicks: 0
     *                  },
     *                  templateCode: `
     *                      <div>
     *                          {{ this.numberOfClicks }}
     *                          <button (click)="this.increaseClicksNumber()">
     *                          </button>
     *                      </div>
     *                  `,
     *                  sharedPrototypeMethods: {
     *                      increaseClicksNumber: function () {
     *                          this.numberOfClicks += 1;
     *                      }
     *                  },
     *                  sharedReferences: {},
     *                  cssStyles: [
     *                      '/some/styles/style.css'
     *                  ]
     *              },
     *              function (err) {
     *                  if (err) console.error(err);
     *              }
     *          );
     *      }).catch(console.error);
     * @class
     * @name JSTemplateComponent
     * @param {string} tagName
     * @param {JSTemplateComponent.options} options
     * @param {JSTemplateComponent.constructorCallback} callback
     */;

    /**
     * @memberof JSTemplateComponent
     * @callback Builder
     * @param {string} tagName
     * @param {JSTemplateComponent.options} options
     * @param {JSTemplateComponent.constructorCallback} callback
     * @returns {JSTemplateComponent}
     */

    /**
     * @memberof JSTemplateComponent
     * @interface contextInstance
     */
    /**
     * @memberof JSTemplateComponent.contextInstance
     * @method redraw
     */
    /**
     * @memberof JSTemplateComponent.contextInstance
     * @method redrawForce
     */
    /**
     * @memberof JSTemplateComponent.contextInstance
     * @name references
     * @type {Object<string,any>}
     */
    /**
     * @memberof JSTemplateComponent.contextInstance
     * @name node
     * @type {HTMLElement}
     */

    /**
     * @memberof JSTemplateComponent
     * @typedef {object} contextLifeCycle
     * @property {JSTemplateComponent.lifeCycleCallback} [init] callbacks on init
     * @property {JSTemplateComponent.lifeCycleCallbackGetReferences} [getReferences] returns
     * @property {JSTemplateComponent.lifeCycleCallback} [contentChange] callback on content change
     * @property {JSTemplateComponent.lifeCycleCallback} [attrChange] context object default is Node
     * @property {JSTemplateComponent.lifeCycleCallback} [remove] context object default is Node
     */

    /**
     * @memberof JSTemplateComponent
     * @interface contextWithInstance
     * @implements JSTemplateComponent.contextInstance
     */

    /**
     * @memberof JSTemplateComponent.contextWithInstance
     * @name __instance
     * @type {JSTemplateComponent.contextInstance}
     */
    /**
     * @memberof JSTemplateComponent.contextWithInstance
     * @description context object default is Node
     * @name __lifeCycle
     * @type {JSTemplateComponent.contextLifeCycle}
     */
    /**
     * @memberof JSTemplateComponent.contextWithInstance
     * @description context object default is Node
     * @name state
     * @type {Object<string,(string|number|Object<string,any>|null|any[])>}
     */

    /**
     * @memberof JSTemplateComponent
     * @interface contextWithoutInstance
     */
    /**
     * @memberof JSTemplateComponent.contextWithoutInstance
     * @description context object default is Node
     * @name __lifeCycle
     * @type {JSTemplateComponent.contextLifeCycle}
     */
    /**
     * @memberof JSTemplateComponent.contextWithoutInstance
     * @description context object default is Node
     * @name state
     * @type {Object<string,(string|number|Object<string,any>|null|any[])>}
     */

    /**
     * @memberof JSTemplateComponent
     * @typedef {object} options
     * @property {function ():JSTemplateComponent.contextWithoutInstance} [context] context object default is Node
     * @property {JSTemplateComponent.contextLifeCycle} [__lifeCycle] context object default is Node
     * @property {string} [templateCode] template Code
     * @property {string} [templateUrl] template URL
     * @property {Array<string>} [cssStyles] list of URLs that point to styles
     * @property {object} [sharedReferences] references that will be same for all created components
     * @property {object} [sharedPrototypeMethods] methods that will be on all components under method "methods"
     * @property {boolean} [__flag_RejectOnStylesError=false]
     */

    /**
     * @memberof JSTemplateComponent
     * @callback lifeCycleCallbackGetReferences
     * @param {JSTemplateComponent.contextWithInstance} context
     * @param {object} sharedReferences
     * @param {object} sharedPrototypeMethods
     * @returns {object}
     */

    /**
     * @memberof JSTemplateComponent
     * @callback lifeCycleCallback
     * @param {JSTemplateComponent.contextWithInstance} context
     * @param {Object<string,any>} references
     * @param {Object<string,any>} methods
     */

    /**
     * @memberof JSTemplateComponent
     * @callback constructorCallback
     * @param {Error} err
     */

     function JSTemplateComponentConstructor(
    	/**
    	 * @private
    	 * @type {string}
    	 */
    	tagName,
    	/**
    	 * @private
    	 * @type {JSTemplateComponent.options}
    	 */
    	options,
    	/**
    	 * @private
    	 * @type {JSTemplateComponent.constructorCallback}
    	 */
    	callback
    ) {
    	//@ts-ignore
    	Application.require([
    		"uriLoad	:: uri-load",
    		"customElements :: custom-elements",
    		"JSTemplate	 :: js-template",
    		"request"
    	]).then(function (lib) {
    			/**
    			 * @private
    			 * @var {object} __instance
    			 * @property {string} templateCode
    			 */
    			var __instance  = {
    				templateCode : ''
    			};

    			function __getTemplate() {
    				if (options.templateCode) {
    					return Promise.resolve(options.templateCode);
    				} else if (options.templateUrl) {
    					return new Promise(function (resolve, reject) {
    						new lib.request().url(options.templateUrl)
    							.response("text")
    							.then(function (templateCode) {
    								__instance.templateCode = templateCode;
    								resolve();
    							}).catch(reject);
    					});
    				} else {
    					return Promise.reject(Error('[OPTIONS_ERROR]: should be defined `templateCode` or `templateUrl`'));
    				}
    			}

    			function __loadCssStyles() {
    				if (!options.cssStyles.length) {
    					return Promise.resolve();
    				}
    				return new Promise(function (resolve, reject) {
    					lib.uriLoad.link(
    						options.cssStyles,
    						function (err) {
    							if (err) {
    								if (options.__flag_RejectOnStylesError) {
    									reject(err);
    								} else {
    									resolve();
    								}
    							} else {
    								resolve();
    							}
    						}
    					);
    				});
    			}

    			__getTemplate().then(
    				function () {
    					__loadCssStyles().then(
    						function () {
    							var sharedPrototypeMethods = options.sharedPrototypeMethods || {};
    							lib.customElements.registerMethods(
    								tagName,
    								Object.assign(
    									sharedPrototypeMethods,
    									{
    										"__onInit"	:
    										/**
    										 * @private
    										 * @this {HTMLElement}
    										 */
    										function () {
    											/**
    											 * @private
    											 * @type {HTMLElement}
    											 */
    											var node = this;
    											/**
    											 * @private
    											 * @type {JSTemplateComponent.contextWithInstance}
    											 */
    											var _contextDefault = {
    													setState   : function (state, callback) {
    														ComponentContext.state = Object.assign(
    															ComponentContext.state || {},
    															state || {}
    														);

    														ComponentContext.__requireRender = true;
    														ComponentContext.__instance.redraw();

    														if (callback) callback();
    													},
    													__requireRender: true,
    													__instance : {
    														node   : node,
    														redraw : function () {
    															if (!ComponentContext.__requireRender) return;
    															//@ts-ignore
    															if (node.attrdata.JSRenderer) {
    																//@ts-ignore
    																ComponentContext.__requireRender = false;

    																node.attrdata.JSRenderer.content.redraw();
    																// /**
    																//  * @deprecated
    																//  */
    																// node.attrdata.JSRenderer.attr.redraw();
    															}
    														},
    														redrawForce: function () {
    															ComponentContext.__requireRender = true;
    															ComponentContext.__instance.redraw();
    														}
    													},
    													__lifeCycle: {
    														init: function () {
    															if (options.__lifeCycle && options.__lifeCycle.init) {
    																options.__lifeCycle.init(
    																	ComponentContext,
    																	envReferences,
    																	sharedPrototypeMethods
    																);
    															}
    														},
    														attrChange: function () {
    															if (options.__lifeCycle && options.__lifeCycle.attrChange) {
    																options.__lifeCycle.attrChange(
    																	ComponentContext,
    																	envReferences,
    																	sharedPrototypeMethods
    																);
    															}
    														},
    														getReferences: function () {
    															// dummy function
    														},
    														contentChange: function () {
    															if (options.__lifeCycle && options.__lifeCycle.contentChange) {
    																options.__lifeCycle.contentChange(
    																	ComponentContext,
    																	envReferences,
    																	sharedPrototypeMethods
    																);
    															}
    														},
    														remove: function () {
    															if (options.__lifeCycle && options.__lifeCycle.remove) {
    																options.__lifeCycle.remove(
    																	ComponentContext,
    																	envReferences,
    																	sharedPrototypeMethods
    																);
    															}
    														}
    													}
    												};

    											var sharedReferences = options.sharedReferences || {};

    											/**
    											 * @private
    											 * @type {JSTemplateComponent.contextWithInstance}
    											 */
    											var ComponentContext = Object.assign(
    												options.context ? (options.context(_contextDefault, sharedReferences, sharedPrototypeMethods) || {}) : {},
    												_contextDefault
    											);

    											var envReferences = Object.assign({}, sharedReferences);

    											if (options.__lifeCycle && options.__lifeCycle.getReferences) {
    												Object.assign(
    													envReferences,
    													options.__lifeCycle.getReferences(
    														ComponentContext,
    														sharedReferences,
    														sharedPrototypeMethods
    													) || {}
    												);
    											}

    											ComponentContext.__instance.references = envReferences;

    											node.innerHTML	= __instance.templateCode;

    											// @ts-ignore
    											node.attrdata.JSRenderer	= {
    												ComponentContext: ComponentContext,
    												envReferences: envReferences,
    												content : lib.JSTemplate.parseContent(node, function () {}, {
    													context : ComponentContext,
    													args	 : envReferences
    												}),
    												attr	: lib.JSTemplate.parseAttributes(node, function () {}, {
    													context : ComponentContext,
    													args	 : envReferences
    												})
    											};

    											if (options.__lifeCycle && options.__lifeCycle.init) {
    												options.__lifeCycle.init.apply(
    													ComponentContext,
    													[
    														ComponentContext,
    														envReferences,
    														sharedPrototypeMethods
    													]
    												);
    											}

    											ComponentContext.__instance.redraw();
    										},
    										"__onContentChange" :
    										/**
    										 * @private
    										 * @this {HTMLElement}
    										 */
    										function () {
    											if (this.attrdata.JSRenderer && this.attrdata.JSRenderer.ComponentContext) {

    												if (
    													this.attrdata.JSRenderer.ComponentContext.__requireRender
    												// 	!this.attrdata.JSRenderer.ComponentContext.__instance.__renderStarted
    												) {
    													if (options.__lifeCycle && options.__lifeCycle.contentChange) {
    														options.__lifeCycle.contentChange.apply(
    															this.attrdata.JSRenderer.ComponentContext,
    															[
    																this.attrdata.JSRenderer.ComponentContext,
    																this.attrdata.JSRenderer.envReferences,
    																this.methods
    															]
    														);
    													}

    													this.attrdata.JSRenderer.ComponentContext.__instance.redraw();
    												}
    											}
    										},
    										"__onAttrChange" :
    										/**
    										 * @private
    										 * @this {HTMLElement}
    										 */
    										function () {
    											if (this.attrdata.JSRenderer && this.attrdata.JSRenderer.ComponentContext) {

    												if (
    													this.attrdata.JSRenderer.ComponentContext.__requireRender
    												// 	!this.attrdata.JSRenderer.ComponentContext.__instance.__renderStarted
    												) {

    													if (options.__lifeCycle && options.__lifeCycle.attrChange) {
    														options.__lifeCycle.attrChange.apply(
    															this.attrdata.JSRenderer.ComponentContext,
    															[
    																this.attrdata.JSRenderer.ComponentContext,
    																this.attrdata.JSRenderer.envReferences,
    																this.methods
    															]
    														);
    													}
    													this.attrdata.JSRenderer.ComponentContext.__instance.redraw();
    												}
    											}
    										},
    										"__onRemove" :
    										/**
    										 * @private
    										 * @this {HTMLElement}
    										 */
    										function () {
    											if (this.attrdata.JSRenderer) {
    												if (options.__lifeCycle && options.__lifeCycle.remove) {
    													options.__lifeCycle.remove.apply(
    														this.attrdata.JSRenderer.ComponentContext,
    														[
    															this.attrdata.JSRenderer.ComponentContext,
    															this.attrdata.JSRenderer.envReferences,
    															this.methods
    														]
    													);
    												}
    											}
    										}
    									}
    								)
    							);
    						}
    					).catch(function (err) {
    						callback(err);
    					});
    				}
    			).catch(function (err) {
    				callback(err);
    			});
    	}).catch(function (err) {
    		callback(err);
    	});

    	return this;
    }

    module.exports = JSTemplateComponentConstructor;