<template>
   <section class="a-carousel">
        <nav ref="viewport" class="a-carousel-viewport">
            <section ref="container" :class="['a-carousel-container', containerClass]">
                <slot name="default"></slot>
            </section>
        </nav>

        <nav class="a-carousel-bullets-container">
            <span class="left">
                <span class="icon bullet" @click="prev()" v-if="showPrev">
                    <i :class="leftButtonIcon"></i>
                </span>
            </span>
            <template v-if="bullets.length > 1">
                <span class="icon bullet" v-bind:key="bullet" v-for="bullet of bullets" @click="select(bullet)">
                    <i v-if="bullet == index" class="fa fa-circle"></i>
                    <i v-else class="far fa-circle"></i>
                </span>
            </template>
            <span class="right">
                <span class="icon bullet" @click="next()" v-if="showNext">
                    <i :class="rightButtonIcon"></i>
                </span>
            </span>

            <slot name="bullets-right" :v-bind="this"></slot>
        </nav>
    </section>
</template>
<style scoped>
.a-carousel {
    width: 100%;
    position: relative;
}

.a-carousel-viewport {
    width: 100%;
    overflow-x: hidden;
}

.a-carousel-container {
    display: flex;
    flex-direction: row;
    align-items: left;
}

.a-carousel-container > * {
    flex-shrink: 0;
}

.a-carousel-bullets-container {
    flex-grow: 1;
}

.a-carousel-bullets-container .bullet {
    cursor: pointer;
}

.a-carousel-bullets-container .left {
    min-width: 2rem;
    margin-right: auto;
}

.a-carousel-bullets-container .right {
    min-width: 2rem;
    margin-left: auto;
}

.a-carousel-bullets-container {
    display: flex;
    flex-direction: row;
}
</style>
<script>
import {ref} from 'vue'


class Offset {
    constructor(el, min=null, max=null) {
        this.el = el
        this.rect = el.getBoundingClientRect();
        ({min, max} = this.minmax(min, max))
        this.min = min
        this.max = max
        this.size = max-min
    }

    minmax(min=null, max=null) {
        min = min === null ? this.rect.left : min
        max = max === null ? this.rect.right : max
        return {min, max}
    }

    relative(to) {
        return new Offset(this.el, this.min-to.min, this.max-to.min)
    }
}


class Card extends Offset {
    constructor(el, index) {
        super(el)
        this.index = index
    }

    visible(viewportOffset) {
        return viewportOffset.min <= this.min && viewportOffset.max >= this.max
    }
}


export default {
    setup() {
        return {
            viewport: ref(null),
            container: ref(null),
        }
    },

    data() {
        return {
            cards: [],
            index: 0,
        refresh_: 0,
        }
    },

    props: {
        cardSelector: {type: String, default: ''},
        containerClass: {type: String, default: ''},
        buttonClass: {type: String, default: 'button'},
        leftButtonIcon: {type: String, default: "fas fa-chevron-left"},
        rightButtonIcon: {type: String, default: "fas fa-chevron-right"},
    },

    computed: {
        card() { return this.cards()[this.index] },

        showPrev() {
            return this.index > 0
        },

        showNext() {
            if(!this.cards || this.cards.length <= 1)
                return false

            let last = this.bullets[this.bullets.length-1]
            return this.index != last
        },

        bullets() {
            if(!this.cards || !this.$refs.viewport)
                return []

            let contOff = new Offset(this.$refs.container)
            let viewMax = new Offset(this.$refs.viewport).size
            let bullets = []

            let i = 0;
            let max = viewMax
            bullets.push(i)
            while(i < this.cards.length) {
                // skip until next view
                for(; i < this.cards.length; i++) {
                    let card = this.cards[i].relative(contOff)
                    if(card.max > max) {
                        max = card.min + viewMax
                        bullets.push(i)
                        i++
                        break
                    }
                }
            }
            return bullets
        },
    },

    methods: {
        getCards() {
            if(!this.$refs.container)
                return []
            let nodes = (!this.cardSelector) ?
                [...this.$refs.container.children] :
                [...this.$refs.container.querySelectorAll(this.cardSelector)]
            return nodes.map((el, index) => new Card(el, index))
        },

        select(index, relative=false) {
            if(relative)
                index = this.index + index

            index = Math.min(index, this.cards.length)
            index = Math.max(index, 0)
            let card = this.cards[index]
            if(!card)
                return null;

            card = new Card(card.el)
            const cont = new Offset(this.$refs.container)
            const rel = card.relative(cont)
            this.$refs.container.style.marginLeft = `-${rel.min}px`
            this.index = index;
            return card.el
        },

        next() {
            let n = this.bullets.indexOf(this.index)
            let index = this.bullets[n+1]
            this.select(index)
        },

        prev() {
            let n = this.bullets.indexOf(this.index)
            let index = this.bullets[n-1]
            this.select(index)
        },

        refresh() {
            this.cards = this.getCards()
            this.select(this.index)
            this.refresh_++
        }
    },


    mounted() {
        this.observers = [
            new MutationObserver(() => this.refresh()),
            new ResizeObserver(() => this.refresh())
        ]
        this.observers[0].observe(this.$refs.container, {"childList": true})
        this.observers[1].observe(this.$refs.container)
        this.refresh()
    },

    unmounted() {
        for(var observer of this.observers)
            observer.disconnect()
    }
}
</script>
