

class Buildoz extends HTMLElement {
    constructor(){
        super()              // always call super() first!
        this.attrs = {}
    }
        
    static get observedAttributes(){ //observable  attributes triggering attributeChangedCallback
        // anything added here will be observed for all buildoz tags
        // in your child, add you local 'color' observable attr with :
        // return([...super.observedAttributes, 'color'])
        return(['name']) 
    }

    static define(name, cls){
        const tag = `bz-${name}`
        if(!customElements.get(tag)) { // no wild redefinition
            customElements.define(tag, cls)
        }
        return cls
    }
    
    connectedCallback(){ // added to the DOM
        this.classList.add('buildoz')
    }
    
    disconnectedCallback(){ // removed from the DOM

    }  

    attributeChangedCallback(name, oldValue, newValue) {
        this.attrs[name] = newValue
        if(name=='name') this.name = newValue
    }

    getBZAttribute(attrName){ // Little helper for defaults
        return(this.getAttribute(attrName) || this.defaultAttrs[attrName] )
    }
}

class BZselect extends Buildoz {
    #value
    #fillFromMarkup = true

    constructor(){
        super()
        this.value = null
        this.open = false
        this.value = null
        this.generalClickEvent = null
        this.defaultAttrs = {
            label: 'Select...',
        }
    }

    connectedCallback() { 
        super.connectedCallback()
        this.button = document.createElement('button')
        this.button.textContent = this.getBZAttribute('label')
        this.prepend(this.button)
        this.button.addEventListener('click', this.toggle.bind(this))
        if(!this.optionscontainer) this.optionscontainer = document.createElement('div')
        this.optionscontainer.classList.add('options-container')
        this.append(this.optionscontainer)
        this.options = this.querySelectorAll('option')
        if(this.#fillFromMarkup){ //can only do it once and only if fillOptions was not already called !!
            for(const opt of this.options){ 
                this.optionscontainer.append(opt) // Will move is to the right parent
                opt.addEventListener('click', this.onClick.bind(this))
                if(opt.getAttribute('selected') !== null) this.onOption(opt.value, true)
            }
            this.#fillFromMarkup = false
        }

    }
    
    // static get observedAttributes(){ // Only if you want actions on attr change
    //     return([...super.observedAttributes, 'disabled'])
    // }
    
    // attributeChangedCallback(name, oldValue, newValue) {
    //     super.attributeChangedCallback(name, oldValue, newValue)
    // }

    get value(){ return(this.#value) }
    
    set value(v) {
        this.#value = v
        if(this.options && this.options.length>0) {
            const opt = Array.from(this.options).find(opt => opt.value==v)
            if(v || (opt && opt.textContent)) this.button.textContent = opt.textContent
            else this.button.textContent =  this.getBZAttribute('label')
            this.dispatchEvent(new Event('change', {
                bubbles: true,
                composed: false,
                cancelable: false
            })) 
        }
    }

    toggle(){ 
        for(const opt of this.options){
            if(this.open) {
                opt.classList.remove('open')
                this.optionscontainer.classList.remove('open')
            } else {
                document.querySelectorAll('bz-select').forEach((sel) => {
                    if((sel!==this) && sel.open) sel.toggle()
                })
                opt.classList.add('open')
                this.optionscontainer.classList.add('open')
            }
        }
        this.open = !this.open
    }

    onClick(evt){ 
        evt.stopPropagation()
        const opt = evt.target.closest('option')
        if(opt && opt.value) this.onOption(opt.value) 
    }

    onOption(value, silent=false){ 
        if(this.getAttribute('disabled') !== null) return
        this.value = value
        if(!silent) this.toggle()     
    }

    addOption(value, markup){
        // Caution: you cannot count on connectedCallback to have run already, because one might fill before adding to the DOM
        const opt = document.createElement('option')
        opt.setAttribute('value', value)
        opt.innerHTML = markup
        opt.addEventListener('click',this.onClick.bind(this))
        if(!this.optionscontainer) this.optionscontainer = document.createElement('div')
        this.optionscontainer.append(opt)
        this.options = this.querySelectorAll('option')
        this.#fillFromMarkup = false
    }

    fillOptions(opts, erase = true){ 
        // Caution: you cannot count on connectedCallback to have run already, because one might fill before adding to the DOM
        if(erase){
            this.options = this.querySelectorAll('option') 
            this.options.forEach(node => { node.remove() })
            this.options = this.querySelectorAll('option')
            this.onOption('', true) // unselect last
        } 
        for(const opt of opts) this.addOption(opt.value, opt.markup)
    }
}
Buildoz.define('select', BZselect)


class BZtoggler extends Buildoz {
    #value

    constructor(){
        super()
        this.open = false
        this.defaultAttrs = {
            labelLeft:'',
            labelRight:'',
            trueValue: 'yes',
            falseValue: 'no',
            classOn:'',
            classOff:'',
            tabindex:0,
            disabled:false
        }
    }

    connectedCallback(){
        super.connectedCallback()

        this.labelRight = document.createElement('div')
        this.labelRight.classList.add('toggle-label-right')
        this.labelRight.innerHTML = this.getBZAttribute('labelRight')

        this.labelLeft = document.createElement('div')
        this.labelLeft.classList.add('toggle-label-left')
        this.labelLeft.innerHTML = this.getBZAttribute('labelLeft')

        this.switch = document.createElement('div')
        this.switch.classList.add('toggle-switch')
        
        this.toggleBar = document.createElement('span')
        this.toggleBar.classList.add('toggle-bar')
        
        this.thumb = document.createElement('span')
        this.thumb.classList.add('toggle-thumb')
        this.toggleBar.append(this.thumb)
        
        this.switch.append(this.toggleBar)

        this.appendChild(this.labelLeft)
        this.appendChild(this.switch)
        this.appendChild(this.labelRight)
        this.setAttribute('tabindex', this.getBZAttribute('tabindex'))

        this.switch.addEventListener('click', this.toggle.bind(this))

    }
    
    turnOn() {
        if(this.getBZAttribute('classOff')) this.switch.classList.remove(this.getBZAttribute('classOff'))
        if(this.getBZAttribute('classOn')) this.switch.classList.add(this.getBZAttribute('classOn'))
        this.thumb.classList.add('turned-on')
        this.#value = this.getBZAttribute('trueValue')
        this.focus()
    }

    turnOff() {
        if(this.getBZAttribute('classOn')) this.switch.classList.remove(this.getBZAttribute('classOn'))
        if(this.getBZAttribute('classOff'))this.switch.classList.add(this.getBZAttribute('classOff'))
        this.thumb.classList.remove('turned-on')
        this.#value = this.getBZAttribute('falseValue')
        this.focus()
    }

    toggle(event) {
        if(event) { event.preventDefault(); event.stopPropagation(); }
        if(this.getBZAttribute('disabled')) return
        if(this.#value == this.getBZAttribute('trueValue')) this.turnOff()
        else this.turnOn()
        this.dispatchEvent(new Event('change', {
            bubbles: true,
            composed: false,
            cancelable: false
        })) 
    }

    get value(){ return(this.#value) }
    
    set value(v) {
        this.#value = v
        if(this.defaultAttrs){
            if(this.#value == this.getBZAttribute('trueValue')) this.turnOn()
            else this.turnOff()
            this.dispatchEvent(new Event('change', {
                bubbles: true,
                composed: false,
                cancelable: false
            }))         
        } 
    }    
}
Buildoz.define('toggler', BZtoggler)



class BZslidePane extends Buildoz {

    constructor(){
        super()
        this.open = false
        this.defaultAttrs = {
            side: 'bottom'
        }
        this.dragMove = this.dragMove.bind(this)
        this.dragEnd = this.dragEnd.bind(this)
        // Fill with innerHTML or other DOM manip should not allow coating to be removed
        this._observer = new MutationObserver(muts => { this.coat() })
    }

    connectedCallback(){
        super.connectedCallback()
        this.coat()
        this._observer.observe(this, { childList: true }) // Do this last
    }

    disconnectedCallback() {
        super.disconnectedCallback()
        this._observer.disconnect()
    }

    coat(){ 
        if(this.handle && this.querySelector(this.dispatchEvent.handle)) return
        this._observer.disconnect()  
        if(this.querySelector(this.dispatchEvent.handle)) this.querySelector(this.dispatchEvent.handle).remove()
        this.handle = document.createElement('div')
        this.handle.classList.add('handle')
        this.prepend(this.handle)
        this.handle.addEventListener('pointerdown', this.dragStart.bind(this))
        this._observer.observe(this, { childList: true })
    }

    dragStart(evt){ 
        evt.target.setPointerCapture(evt.pointerId)        
        this.dragStartX = evt.clientX
        this.dragStartY = evt.clientY        
        this.handle.addEventListener('pointermove', this.dragMove)
        this.handle.addEventListener('pointerup', this.dragEnd)
    }

    dragMove(evt){
        const box = this.getBoundingClientRect()
        const parentBox = this.parentElement.getBoundingClientRect()
        let width, height
        switch(this.getAttribute('side')){
            case 'top':
                height = (evt.clientY > box.top) ? (evt.clientY - box.top) : 0
                if(height>(parentBox.height/2)) height = Math.floor(parentBox.height/2)
                this.style.height = height+'px'
                break
            case 'bottom':
                height = (evt.clientY < box.bottom) ? (box.bottom - evt.clientY) : 0
                if(height>(parentBox.height/2)) height = Math.floor(parentBox.height/2)
                this.style.height = height+'px'
                break
            case 'left':
                width = (evt.clientX > box.left) ? (evt.clientX - box.left) : 0
                if(width>(parentBox.width/2)) width = Math.floor(parentBox.width/2)
                this.style.width = width+'px'
                break
            case'right':
                width = (evt.clientX < box.right) ? (box.right - evt.clientX) : 0
                if(width>(parentBox.width/2)) width = Math.floor(parentBox.width/2)
                this.style.width = width+'px'
                break
        }
    }

    dragEnd(evt){
        evt.target.releasePointerCapture(evt.pointerId)
        this.handle.removeEventListener('pointermove', this.dragMove)
        this.handle.removeEventListener('pointerup', this.dragEnd)
    }
}
Buildoz.define('slidepane', BZslidePane)

