import { LasalRuntimeSigElement, SigPolymer } from '../../sigmatek/sig-element/sig-element.js';

export class LasalRuntimeSigControlaccordion extends LasalRuntimeSigElement {

    /****************************************************************************************************
     * define the custom tag name 
     *
     * @readonly
     * @static
     * @memberof LasalRuntimeSigControlaccordion
    ****************************************************************************************************/
    static get is() {
        return "sig-control-accordion";
    }

    /****************************************************************************************************
     * When the element is loaded, importMeta() assigns reference to the path from 
     * which an element was imported to its importPath property. 
     * Relative URLs in styles are automatically re-written to be relative to the importPath property. 
     * You can use [[importPath]] in your component to load url resources
     *
     * @readonly
     * @static
     * @memberof LasalRuntimeSigControlaccordion
    ****************************************************************************************************/
    static get importMeta() {
        return import.meta;
    }

    /****************************************************************************************************
     * Return the component style and template as a template literal
     *
     * @readonly
     * @static
     * @memberof LasalRuntimeSigControlaccordion
    ****************************************************************************************************/
    static get template() {
        return SigPolymer.html`
        <style include="sig-element-css">
            :host {
                   
            display: block;
            position: absolute;
            overflow: hidden;
        }

        :host([isdesignmode]) {
            background-image: url('./preview.svg');
            background-repeat: no-repeat;
            background-size: contain;
            background-position: center;
        }

        :host * {
            @apply --notextselect;
            margin: 0;
            padding: 0;
        }

        .clearfix:after {
            @apply --clearfix
        }

        .sig-control-accordion {
            position: relative;  
            -ms-overflow-style: none;  /* IE and Edge */
            scrollbar-width: none;  /* Firefox */
        }
        
        /* Hide scrollbar for Chrome, Safari and Opera */
        .sig-control-accordion::-webkit-scrollbar {
            display: none;
        }

        :host([direction="toptobottom"]) .sig-control-accordion {
            overflow-y: scroll;
            overflow-x: hidden;
            width: 100%;
            height: 100%;
        }


        :host([direction="lefttoright"]) .sig-control-accordion {
            overflow-y: hidden;
            overflow-x: scroll;
            height: 100%;
            width: 100%;
        }

        .container {
            position: absolute;
            top: 0;
            left: 0;
            /* disable default scroll-anchor behaviour so browser doesn't decide if content is being opened before or after tabs -> here: open after tabs */
            overflow-anchor: none; 
        }

        :host([direction="lefttoright"]) .container {
            white-space: nowrap;
        }

        slot {
            display: block;
        }
        :host ::slotted(sig-composite-control) {
            position: relative !important;
            top: 0;
            left: 0;
        }

        .ccContainer {
            /* to keep the inline elements in the same line */
            white-space: nowrap;

            /* to avoid the space that display-inline-block creates at the bottom of the line */
            line-height: 0;
        }

        :host([direction="lefttoright"]) .ccContainer {
            display: inline-block;
            vertical-align: top;
        }
        .ccContainer * {
            line-height: initial;
        }

        .menu {
            display: inline-flex;
            vertical-align: top;
        }

        :host([direction="toptobottom"]) .menu {
            flex-direction: row;
        }

        :host([direction="lefttoright"]) .menu {
            flex-direction: column;
        }

        .control-container {
            display: inline-block;
        }

        :host([direction="lefttoright"]) .control-container {
            vertical-align: top;
        }

        .slotAdvanced-container {
            display: inline-block;
        }

        :host([direction="lefttoright"]) .slotAdvanced-container {
            vertical-align: top;
        }

        :host([direction="toptobottom"]) .slotContent-container {
            display: block;
        }

        :host([direction="lefttoright"]) .slotContent-container {
            display: inline-block;
        }

        :host([direction="toptobottom"]) .slotContent-container ::slotted(*) {
            transition: height 0.2s ease-in-out;
        }

        :host([direction="lefttoright"]) .slotContent-container ::slotted(*){
            transition: width 0.2s ease-in-out;
        }

        .slotContent-container {
            overflow: hidden;
        }     

        :host([direction="lefttoright"]) .closed .slotAdvanced-container,
        :host([direction="lefttoright"]) .closed .slotContent-container ::slotted(*),
        :host([direction="lefttoright"]) .rendering .slotAdvanced-container, 
        :host([direction="lefttoright"]) .rendering .slotContent-container ::slotted(*) { 
            width: 0px !important;
            overflow: hidden;
        }


        :host([direction="toptobottom"]) .closed .slotAdvanced-container,
        :host([direction="toptobottom"]) .closed .slotContent-container ::slotted(*),
        :host([direction="toptobottom"]) .rendering .slotAdvanced-container, 
        :host([direction="toptobottom"]) .rendering .slotContent-container ::slotted(*) {
            height: 0px !important;
            overflow: hidden;
        }

        .displayNone {
            display: none !important;
        }
    </style>
    <div class="sig-control-accordion clearfix" id="outerContainer" on-scroll="_handleScroll" on-wheel="_handleWheel">
        <div class="container" id="container">
        </div>
    </div>
    `;
    }

    /****************************************************************************************************
     * define the main porperties of the component
     * reflected property names should always be lower case so it can be used directly on the element as an attribute
     *
     * @readonly
     * @static
     * @memberof LasalRuntimeSigControlaccordion
    ****************************************************************************************************/
    static get properties() {
        let props = {
            value: {
                type: Number,
                value: 0,
                observer: '_valueChanged'
            },
            valueabsolute: {
                type: Number,
                value: 0,
                observer: '_valueabsoluteChanged'
            },
            direction: {
                type: String,
                value: 'toptobottom',
                reflectToAttribute: true
            }
        }
        return props;
    }

    /****************************************************************************************************
     * Creates an instance of LasalRuntimeSigControlaccordion.
     * @memberof LasalRuntimeSigControlaccordion
    ****************************************************************************************************/
    constructor() {
        super();
        this.tabControlScheme = undefined;
        this.tabControlAdvancedScheme = undefined;
        this.tabContentScheme = undefined;
        this.datapointScheme = undefined;

        this.checkbits = undefined;
        this.tabControlCCs = undefined;
        this.tabContentCCs = undefined;
        this.tabControlAdvancedCCs = undefined;
        this.datapoints = undefined;

        this.serverconfig = "value";

        // Flag that determines if overloading should happen
        this.overload = false;
        // Maps the value of the 
        this.valueMap = [];

        // The offsets of the tabs from the top or left
        // Saved in a variable to avoid having to query it constantly
        this.placeholderOffset = [];

        // The height or width of the container
        // Saved in a variable to avoid having to query it constantly
        this.containerSize = 0;

        // The height of the component
        // Saved in a variable to avoid having to query it constantly
        this.size = 0;

        // VM of the control needed to append composite controls
        this.parent = undefined;

        this.scrollDebouncer = undefined;

        this.initDone = false;

        this._checkbitObserver = (newval) => {
            this._checkbitChanged(newval);
        }

        this.topOrLeft = 'top';
        this.heightOrWidth = 'height';

        // add required properties
        this.sigAddRequiredProperty('value');
        this.sigAddRequiredProperty('valueabsolute');
    }

    /****************************************************************************************************
     * called after property default values are set and local DOM is initialized
     *
     * @memberof LasalRuntimeSigControlaccordion
    ****************************************************************************************************/
    ready() {
        super.ready();
        this.isReady = true;
        if (this.propsReady) {
            this._init(); 
        } 
    }

    /****************************************************************************************************
     * called when values are ready
     *
     * @memberof LasalRuntimeSigControlaccordion
    ****************************************************************************************************/
    sigOnRequiredPropertiesReady() {
       super.sigOnRequiredPropertiesReady();
       this.propsReady = true; 
       if (this.isReady) {
        this._init(); 
       }
    }

    /****************************************************************************************************
     * Observer for changes of checkbits
     *
     * @param {*} newval
     * @memberof LasalRuntimeSigControlaccordion
    ****************************************************************************************************/
    _checkbitChanged(newval) {
        this.valueMap = this._updateValueMap();
        this._render();
        this._valueabsoluteChanged(this.valueabsolute);
    }

    /****************************************************************************************************
     * Update of the available tabs, depending on the checkbits
     *
     * @return {*} 
     * @memberof LasalRuntimeSigControlaccordion
    ****************************************************************************************************/
    _updateValueMap() {
        let valueMap = [];
        if(this.checkbits && this.checkbits.length) {
            for(let i = 0; i < this.checkbits.length; i++) {
                if(this.checkbits[i].getValue()) {
                    valueMap.push(i);
                }
            }
        }
        return valueMap;
    }

    /****************************************************************************************************
     * Initialization of the component
     *
     * @return {*} 
     * @memberof LasalRuntimeSigControlaccordion
    ****************************************************************************************************/
    async _init() {
        if(this.isdesignmode) return;
        // Get the data from the schemes
        this.datapoints = await this._getSchemeData('datapointScheme');
        this.tabControlCCs = await this._getSchemeData('tabControlScheme');
        this.tabControlAdvancedCCs = await this._getSchemeData('tabControlAdvancedScheme');
        this.tabContentCCs = await this._getSchemeData('tabContentScheme');

        // Check if the accordion scrolls horizontally or vertically, and set the variables
        if(this.direction !== 'toptobottom') {
            this.heightOrWidth = 'width';
            this.topOrLeft = 'left'
        }

        // Check if the schemes have the same length (otherwise matching and overloading doesn't work)
        if(!this._checkSchemeLengths()) {
            this._log('The schemes are no the same length', 'ERROR');
        }

        // If we have a datapoint scheme, we set the flag to overload the other composite controls
        if(this.datapoints && this.datapoints.length) {
            this.overload = true;
        }

        // After we validated the schemes, we get the checkbits; this needs to be done after,
        // Because we need to make sure the tabControlScheme, and the datapointScheme have the same length
        this.checkbits = this._getCheckbits();

        this.valueMap = this._updateValueMap();
        this._initPlaceholders();
        this.size = parseFloat(this.style.getPropertyValue(`--theme-sig-element-${this.heightOrWidth}`));

        // initialization is done
        this.initDone = true;

        // render tabs
        this._render();

        // check for not appended ccs and append them
        this._appendRemainingCCs();

        // open tabs
        this._openTabs(this.valueabsolute);
    }

    /****************************************************************************************************
     * Adds Placeholders for the tabs
     * this is done so that we have the correct scroll height, without adding all the 
     * composite controls at once
     *
     * @memberof LasalRuntimeSigControlaccordion
    ****************************************************************************************************/
    _initPlaceholders() {
        let currentOffset = 0;
        this.placeholderOffset = [];
        for(let i = 0; i < this.tabControlCCs.length; i++) {
            //creating a container where we render a cc in
            let ccContainer = document.createElement('div');
            ccContainer.classList.add('ccContainer');
            ccContainer.classList.add('not-rendered');

            let menuContainer = document.createElement('div');
            menuContainer.classList.add('menu');
            //The height or width of the container
            let size = 0;
            // Height or Width only needs to be set, ifthe container is shown
            if(this.checkbits[i].getValue()) {
                //The height or width of the container
                size = parseFloat(this.tabControlCCs[i].getControlsJson().props[`--theme-sig-element-${this.heightOrWidth}`].value);

                // If the container that we currently draw is open, we need to add the height or width of the content to the control
                if((this.serverconfig === 'value' &&  this.valueabsolute === (i + 1)) ||
                    (this.serverconfig !== 'value' &&  (this.valueabsolute & 1 << i))) {
                    size += parseFloat(this.tabContentCCs[i].getControlsJson().props[`--theme-sig-element-${this.heightOrWidth}`].value);
                }            

                if(this.direction !== 'lefttoright') {
                    ccContainer.style.setProperty(`${this.heightOrWidth}`, size + 'px');
                }
            }

            //We save away the offset from the top or left of the current placeholder
            //because we need that information to determine which CCs need to be rendered;
            this.placeholderOffset.push(currentOffset);
            currentOffset += size;

            //adding the slot to the container
            let slot = document.createElement('slot');
            slot.name = i;
            let slotAdvanced,slotContent;
            if(this.tabControlAdvancedCCs && this.tabControlAdvancedCCs.length) {
                slotAdvanced = document.createElement('slot');
                slotAdvanced.name = 'adv-'+i;
            }
            slotContent = document.createElement('slot');
            slotContent.name = 'cont-'+i;

            // The slot for the control in its own container
            let controlContainer = document.createElement('div');
            controlContainer.classList.add('control-container');
            controlContainer.append(slot);
            menuContainer.append(controlContainer);

            // The slot for advanced control in its own container
            let slotAdvancedContainer = document.createElement('div');
            slotAdvancedContainer.classList.add('slotAdvanced-container');
            if(slotAdvanced) {
                slotAdvancedContainer.append(slotAdvanced);
                menuContainer.append(slotAdvancedContainer);
            }
            
            // The slot for the content in its own container
            let slotContentContainer = document.createElement('div');
            slotContentContainer.classList.add('slotContent-container');
            slotContentContainer.append(slotContent);
            ccContainer.append(menuContainer);
            ccContainer.append(slotContentContainer);

            this.$.container.append(ccContainer);
        }

        this.containerSize = currentOffset;
    }

    /****************************************************************************************************
     * Overriding the default sigApplyCSSValue function
     * needed because the component needs to be redrawn on some css changes
     *
     * @param {*} cssObj
     * @param {boolean} [doBoundsCheck=false]
     * @memberof LasalRuntimeSigControlaccordion
    ****************************************************************************************************/
    sigApplyCSSValue(cssObj, doBoundsCheck = false) {
        super.sigApplyCSSValue(cssObj, doBoundsCheck);
        if(cssObj.hasOwnProperty(`--theme-sig-element-${this.heightOrWidth}`)) {
            this.size = parseFloat(cssObj[`--theme-sig-element-${this.heightOrWidth}`]);
        }
    }

    /****************************************************************************************************
     * Renders the visible CCs
     *
     * @return {*} 
     * @memberof LasalRuntimeSigControlaccordion
    ****************************************************************************************************/
    _render() {
        // We need to finish the initialisation
        // We also need a height; Otherwise we cannot determine which elements need to be rendered
        if(!this.size || !this.initDone) return;

        let scrollTopOrLeft = this.$.outerContainer[`scroll${this._capitalizeFirstLetter(this.topOrLeft)}`];
        if(!this.parent) {
            this.parent = parent = sigApi.vm.getVmById(this.id);
        }
        this.ccContainers = this.$.container.querySelectorAll('.ccContainer');
        for(let i=0; i<this.tabControlCCs.length; i++) {
            //check if cc should be displayed
            if (this.checkbits[i].getValue()) {
                this.ccContainers[i].classList.remove('displayNone');     

                //check if cc not appended yet
                if (this.ccContainers[i].ccAppended !== true) {
                    // Check if the container is in the visible area
                    if((this.placeholderOffset[i] <= scrollTopOrLeft && this.placeholderOffset[i+1] >= scrollTopOrLeft) || 
                    (this.placeholderOffset[i] >= scrollTopOrLeft && this.placeholderOffset[i] <= (scrollTopOrLeft + this.size))) {
                        let overload = null;
                        if(this.datapoints && (this.datapoints[i] || this.datapoints[i] === 0)) {
                            overload = this.datapoints[i];
                        }
                        //append cc
                        this._appendControlCC(this.ccContainers[i], this.parent, this.tabControlCCs[i].getName(), ''+i, overload);
                    }                    
                }  
            } else {
                this.ccContainers[i].classList.add('displayNone');     
            }
        }
    }


    /****************************************************************************************************
     * Method is used to append a control cc
     *
     * @param {*} container
     * @param {*} parentVM
     * @param {*} ccName
     * @param {*} slotName
     * @param {*} overload
     * @memberof LasalRuntimeSigControlaccordion
     ****************************************************************************************************/
    _appendControlCC(container, parentVM, ccName, slotName, overload) {
        if ((container) && (parentVM) && (ccName) && (slotName)) {
            const appended = sigApi.vm.appendNewCC(parentVM, ccName, slotName, overload);
            if (appended) {
                container.classList.remove('not-rendered');
                container.style.removeProperty('height');
                container.style.removeProperty('display'); 
                //set cc appended
                container.ccAppended = true; 
            }
        }
        else {
            this._log('_appendControlCC(): invalid inputs', 'ERROR');
        }
    }

    /****************************************************************************************************
     * Method is used to append ccs which are not appended yet
     *
     * @memberof LasalRuntimeSigControlaccordion
     ****************************************************************************************************/
    _appendRemainingCCs() {
        if ((this.tabControlCCs !== undefined) && (this.tabControlCCs.length > 0)) {            
            if ((this.ccContainers) && (this.ccContainers.length === this.tabContentCCs.length)) {
                for (let i=0; i<this.tabControlCCs.length; i++) {
                    //check if not appended yet
                    if (this.ccContainers[i].ccAppended !== true) {
                        //request idle callback not working on safari
                        if (requestIdleCallback) {
                            //request idle callback to append this cc
                            requestIdleCallback(()=> {
                                // check if it has not been appended since and control is still connected
                                if (this.ccContainers[i].ccAppended !== true && this.isConnected) {
                                    let overload = null;
                                    
                                    if(this.datapoints && (this.datapoints[i] || this.datapoints[i] === 0)) {
                                        overload = this.datapoints[i];
                                    }
                                    this._appendControlCC(this.ccContainers[i], sigApi.vm.getVmById(this.id), this.tabControlCCs[i].getName(), ''+i, overload);                            
                                }
                            }, {timeout: 100});                            
                        }
                        else {
                           //schmar2: think about alternative for safari 
                        }
                    }
                }                
            }           
            else {
                this._log('_appendRemainingCCs(): amount of containers and ccs not equal', 'ERROR')
            }
        }
    }

    /****************************************************************************************************
     * Method to capitalize first letter
     *
     * @param {*} string
     * @return {*} 
     * @memberof LasalRuntimeSigControlaccordion
    ****************************************************************************************************/
    _capitalizeFirstLetter(string) {
        return string.charAt(0).toUpperCase() + string.slice(1);
      }

    /****************************************************************************************************
     * Checks if the required schemes are the same length
     * and if optional schemes are set, their length is checkt as well
     *
     * @return {*} 
     * @memberof LasalRuntimeSigControlaccordion
    ****************************************************************************************************/
    _checkSchemeLengths() {
        if(this.tabControlCCs && this.tabContentCCs && this.tabControlCCs.length === this.tabContentCCs.length) {
            if((this.tabControlAdvancedCCs && this.tabControlCCs.length === this.tabControlAdvancedCCs.length) || !this.tabControlAdvancedCCs) {
                if((this.datapoints && this.tabControlCCs.length === this.datapoints.length) || !this.datapoints) {
                    return true;
                }
            }
        }
        return false;
    }

    /****************************************************************************************************
     * Here we get the checkbits, this needs to be done after we get the 
     *
     * @return {Array} - The checkbits, already overloaded with the correct datapoint
     * @memberof LasalRuntimeSigControlaccordion
    ****************************************************************************************************/
    _getCheckbits() {
        let checkbits = [];
        
        for(let cc in this.tabControlCCs) {
            let props = this.tabControlCCs[cc].getControlsJson().props;
            if(props.checkbit){
                let checkbit = sigApi.properties.createProperty({
                    src: sigApi.SIG_CONST.PROP_SRC_CHECKBIT,
                    dest: sigApi.SIG_CONST.PROP_DEST_NONE,
                    value: props.checkbit.value.split(':')[1]
                })
                if(this.overload) {
                    checkbit.overload(this.datapoints[cc])
                }
                checkbit.addObserver(this._checkbitObserver);
                checkbits.push(checkbit);

            } else {
                // If there is no checkbit, we return an object with a get value function that is always true
                // to keep the handling consistent
                checkbits.push({
                    getValue: () => {return true}
                })
            }
        }
        return checkbits;
    }

    /****************************************************************************************************
     * Gets the data of a scheme
     *
     * @param {String} propName - Name of the property where the scheme is applied
     * @returns {Array | undefined} - the scheme data, or undefined, if no scheme was found
     * @memberof LasalRuntimeSigControlaccordion
     ****************************************************************************************************/
    async _getSchemeData(propName) {
        let schemeContents = undefined;
        if(propName === 'datapointScheme') {
            schemeContents = await sigApi.properties.getSchemeBranchDataWithProperty(this.id, propName).catch(() => {});
            if (schemeContents) {
                schemeContents = schemeContents.map((entry) => {
                    return entry.resultPropertyObject.value;
                });                   
            }
        } else {
            schemeContents = await sigApi.properties.getSchemeDataWithProperty(this.id, propName).catch(() => {});
            if (schemeContents) {
                schemeContents = schemeContents.map((entry) => {
                    return entry.value;
                }); 
            }
        }
        return schemeContents;
    }
    
    /****************************************************************************************************
     * Observer for value
     *
     * @param {*} newval new value of server
     * @return {*} 
     * @memberof LasalRuntimeSigControlaccordion
    ****************************************************************************************************/
    _valueChanged(newval) {

        // check if component is ready to rumble
        if (this.size && this.initDone) {

            // calculate absolute value: value for tab
            if (this.serverconfig === "value") {
                if (this.valueMap[newval - 1] || this.valueMap[newval - 1] === 0) {
                    this.valueabsolute = this.valueMap[newval - 1] + 1;
                } else {
                    this.valueabsolute = 0;
                }

            // calculate absolute value: bit for tab
            } else {
                if (newval >= 0) {
                    let value = 0;
                    this.valueMap.forEach((element, index) => {
                        if (newval & 1 << index) {
                            value = value | 1 << this.valueMap[index];
                        }
                    });
                    this.valueabsolute = value;
                } else {
                    this.valueabsolute = 0;
                }
            }

            // dispatch event to update property and server
            this.dispatchEvent(new CustomEvent('value-changed'));
        } 
    }
    
    /****************************************************************************************************
     * Observer for valueabsolute
     *
     * @param {*} newval new value of server
     * @return {*} 
     * @memberof LasalRuntimeSigControlaccordion
    ****************************************************************************************************/
    _valueabsoluteChanged(newval) {
        
        // check if component is ready to rumble
        if (this.size && this.initDone) {

            // calculate relative value: value for tab
            if (this.serverconfig === "value") {
                this.value = this.valueMap.indexOf(this.valueabsolute-1) + 1;

            // calculate relative value: bit for tab
            } else {
                if (newval >= 0) {
                    let value = 0;
                    let max = Math.max(...this.valueMap) + 1;
                    for (let i = 0; i < max; i ++) {
                        let index = this.valueMap.indexOf(i)
                        if ((newval & 1 << i) && (index !== -1)) {
                            value = value | 1 << index;
                        }
                    }
                    this.value = value;
                } else {
                    this.value = 0;
                }
            }

            // open tabs, absolute
            this._openTabs(newval);

            // dispatch event to update server
            this.dispatchEvent(new CustomEvent('valueabsolute-changed'));
        } 
    }

    /****************************************************************************************************
     * Method to open selected tabs
     *
     * @param {Number} tabs - specifies the tabs to open
     * @param  {boolean} relative - indicates if tabs is a relative or absolute value
     * @return {*} 
     * @memberof LasalRuntimeSigControlaccordion
    ****************************************************************************************************/
    _openTabs(tabs) {
        // value for tab
        //-----------------------------------------------------------------------------------------------
        if (this.serverconfig === "value") {
            // Close all compositeContainers
            for (let i = 0; i < this.ccContainers.length; i ++) {
                if (!this.ccContainers[i].classList.contains('closed') && this.valueabsolute !== (i + 1)) {
                    this.ccContainers[i].classList.add('closed');
                    if (this.ccContainers[i].tabCCAdvancedVM) this.ccContainers[i].tabCCAdvancedVM.setInactive();
                    if (this.ccContainers[i].tabCCContentVM) this.ccContainers[i].tabCCContentVM.setInactive();
                }
            }

            // check if container is available and...
            //  -> is closed || is not rendered || content is not appended 
            if (this.ccContainers[tabs - 1] && 
                (this.ccContainers[tabs - 1].classList.contains('closed') || this.ccContainers[tabs - 1].classList.contains('not-rendered') || !this.ccContainers[tabs - 1].querySelector('.slotContent-container slot').assignedNodes()[0])) {

                // open selected tab
                this._openTab(this.ccContainers[tabs - 1], tabs - 1);
        
                // remove css after next render
                SigPolymer.afterNextRender(this, () => {
                    this.ccContainers[tabs - 1].classList.remove('rendering');
                });
            }

        // bit for tab
        //-----------------------------------------------------------------------------------------------
        } else {
            this.container2render = [];

            // limit length of scheme, because server only has 32bit
            if (this.ccContainers.length > 32) {
                this.ccContainers.length = 32;
            }

            // loop to open all selected container
            for (let i = 0; i < this.ccContainers.length; i ++) {
                
                // check if tab should be opened and is not already opened
                if ((tabs & 1 << i) && 
                    (this.ccContainers[i].classList.contains('closed') || this.ccContainers[i].classList.contains('not-rendered') || !this.ccContainers[i].querySelector('.slotContent-container slot').assignedNodes()[0])) {

                    // open selected tab 
                    this._openTab(this.ccContainers[i], i);

                    // save container for afterNextRender
                    this.container2render.push(this.ccContainers[i]); 

                } else if(!(tabs & 1 << i) && !this.ccContainers[i].classList.contains('closed')) {
                    this.ccContainers[i].classList.add('closed');
                    if (this.ccContainers[i].tabCCAdvancedVM) this.ccContainers[i].tabCCAdvancedVM.setInactive();
                    if (this.ccContainers[i].tabCCContentVM) this.ccContainers[i].tabCCContentVM.setInactive();                    
                }
            }   
            
            // remove css after next render
            SigPolymer.afterNextRender(this, () => {
                this.container2render.forEach(element => {
                    element.classList.remove('rendering'); 
                });
            }) 
        }  
    }

    /****************************************************************************************************
     * Method to open one single tab
     *
     * @param {*} idx
     * @memberof LasalRuntimeSigControlaccordion
    ****************************************************************************************************/
    _openTab(container, idx) {
        // set size of container
        const containerSlot = container.querySelector('.slotContent-container');

        containerSlot.classList.remove('displayNone');     

        ((idx) => {
            containerSlot.ontransitionend = (event) => {
                const parent = event.currentTarget.parentElement;
                const isClosed = parent.classList.contains('closed');

                // if element is not visible, exclude from rendering 
                if (isClosed) event.currentTarget.classList.add('displayNone');
                else if (this.scrollintoview) {
                    // don't use scrollIntoView() because it doesn't allow simultaneous execution
                    // event.currentTarget.scrollIntoView({ behavior: 'smooth' });
                    let offset = 0;
                    const currentScrollPos = this.$.outerContainer[`scroll${this._capitalizeFirstLetter(this.topOrLeft)}`];
    
                    // calculate the current position of the opened tab
                    // -> add all sizes of the elements above
                    for (let index = 0; index < idx; index++) {
                        // only if it is visible
                        if (!this.checkbits[index].getValue()) continue;

                        offset += parseFloat(this.tabControlCCs[index].getControlsJson().props[`--theme-sig-element-${this.heightOrWidth}`].value);
                        
                        // if content is opened add its size too
                        if ((this.valueabsolute & 1 << index) && !this.ccContainers[index].classList.contains('closed')) {
                            //height of content cc can be adjusted so we check if we can fetch the height directly
                            //get cc viewmodel
                            const ccVM = this._getChildBySlot(this.parent, `cont-${index}`);
                            if ((ccVM !== null) && (ccVM !== undefined)) {
                                //get domref
                                const ccDomRef = ccVM.getDomRef();
                                if ((ccDomRef !== null) && (ccDomRef !== undefined)) {
                                    //fetch offset height or width
                                    offset += ccDomRef[`offset${this._capitalizeFirstLetter(this.heightOrWidth)}`];
                                    //go on with next cc
                                    continue;
                                }
                            }
                            offset += parseFloat(this.tabContentCCs[index].getControlsJson().props[`--theme-sig-element-${this.heightOrWidth}`].value);
                        }
                    }

                    const tabSize = parseFloat(this.tabControlCCs[idx].getControlsJson().props[`--theme-sig-element-${this.heightOrWidth}`].value);
                    const elementSize = tabSize + event.currentTarget[`offset${this._capitalizeFirstLetter(this.heightOrWidth)}`]

                    // calc the bottom/right position of the container when opened
                    const newContPos = offset + elementSize;

                    // check if element is still visible, if not scroll it into view
                    // -> if bottom/right pos is greater than the current scroll position plus the components size
                    if (newContPos > currentScrollPos + this.size) {
                        const newScrollPos = currentScrollPos + newContPos - (currentScrollPos + this.size);
                        this.$.outerContainer.scrollTo({ [this.topOrLeft]: newScrollPos, behavior: 'smooth' });
                    }
                }
            };
        })(idx);

        // sett cs for rendering of container
        container.classList.remove('closed');
        container.classList.add('rendering');

        // check if datapoint scheme is available and overload container
        if (!container.tabCCAdvancedVM && !container.tabCCContentVM) {
            let overload = null;
            if (this.datapoints && (this.datapoints[idx] || this.datapoints[idx] === 0)) {
                overload = this.datapoints[idx];
            }
            if (this.tabControlAdvancedCCs && this.tabControlAdvancedCCs[idx]) {
                sigApi.vm.appendNewCC(this.parent, this.tabControlAdvancedCCs[idx].getName(), `adv-${idx}`, overload);
                //save viewmodel of container for later use
                container.tabCCAdvancedVM = this._getChildBySlot(this.parent, `adv-${idx}`);
                // register callback when runtime changes isActive of compositeContainer
                if (container.tabCCAdvancedVM.registerBeforeSetActive) {
                    container.tabCCAdvancedVM.registerBeforeSetActive(() => {
                        // false -> block setActive
                        return (!container.classList.contains('closed'));
                    });
                }
            }
            if (this.tabContentCCs && this.tabContentCCs[idx]) {
                sigApi.vm.appendNewCC(this.parent, this.tabContentCCs[idx].getName(), `cont-${idx}`, overload);
                container.tabCCContentVM = this._getChildBySlot(this.parent, `cont-${idx}`);
                // register callback when runtime changes isActive of compositeContainer
                if (container.tabCCContentVM .registerBeforeSetActive) {
                    container.tabCCContentVM .registerBeforeSetActive(() => {
                        // false -> block setActive
                        return (!container.classList.contains('closed'));
                    });
                }
            }
        }

        // set compositeContainer active
        if (container.tabCCAdvancedVM) {
            container.tabCCAdvancedVM.setActive();
        }
        if (container.tabCCContentVM) {
            container.tabCCContentVM.setActive();
        }
    }


    /****************************************************************************************************
     * helper method to get the vm by the name of the slot
     *
     * @param {*} parentVM - parent viewmodel
     * @param {*} slot - slot to search for
     * @return {*} 
     * @memberof LasalRuntimeSigControlaccordion
     ****************************************************************************************************/
    _getChildBySlot(parentVM, slot) {
        if (parentVM) {
            const children = parentVM.getChildren();
            if ((children) && (children.length > 0)) {
                for (let i=0; i<children.length; i++) {
                    if (slot === children[i].getSlot()) return children[i];
                }
            }
        }
    }

    /****************************************************************************************************
     * On scroll Events, we just debounce the event to render the Tabs
     * so that it only happens once, and not multiple times per second
     *
     * @param {*} e
     * @memberof LasalRuntimeSigControlaccordion
    ****************************************************************************************************/
    _handleScroll(e) {
        if (this.scrollThrottler === undefined) {
            this.scrollThrottler = this.sigSetTimeout(() => {
                this._render();
                this.scrollThrottler = undefined;
            }, 50);
        }
    }

    /****************************************************************************************************
     *
     *
     * @param {*} e
     * @memberof LasalRuntimeSigControlaccordion
    ****************************************************************************************************/
    _handleWheel(e) {
        if(this.direction === 'lefttoright') {
            e.preventDefault();
            this.$.outerContainer.scrollLeft += e.deltaY;
        }
    }

    /****************************************************************************************************
     * This callback is called when the component is disconnected from the DOM.
     * It is used for cleanup
     *
     * @memberof LasalRuntimeSigControlaccordion
    ****************************************************************************************************/
    disconnectedCallback() {
        super.disconnectedCallback();
        for(let i = 0; i<this.checkbits.length; i++) {
           if(this.checkbits[i].destroy) {
               this.checkbits[i].destroy();
           }
       } 
    }

    /****************************************************************************************************
     * Will be called when the runtime shows or hides the component by setting "display" to "block" or "none".
     *
     * @param {boolean} isVisible
     * @memberof LasalRuntimeSigControlaccordion
    ****************************************************************************************************/
    sigOnVisibilityChange(isVisible) {
        if (this.initDone && !isVisible) {
            // for all slotContainers which should be closed add the displayNone
            // -> because transitionend might not have been called
            const slotContentContainer = this.$.outerContainer.querySelectorAll('.closed > .slotContent-container');
            slotContentContainer.forEach((container) => {
                container.classList.add('displayNone');
            });
        }
    }
}
customElements.define(LasalRuntimeSigControlaccordion.is, LasalRuntimeSigControlaccordion);

