import { Namespace } from "../../utils/namespace";
import { mixParent } from "../../mixins/relation/parent";
import { Touch } from "../../utils/touch";
import { Direction } from "../../utils/direction";
import { MathExtension } from "../../utils/extensions/math";
import { DOMExtension } from "../../utils/extensions/dom";
import { doubleRaf } from "../../utils/raf";

export const namespace = Namespace.Create('swiper');

export default {
    name: namespace.name,
    mixins:[
        mixParent(namespace.name)
    ],
    props:{
        autoplay:{
            type: String | Number
        },
        duration:{
            type: String | Number,
            default:500
        },
        defaultActive:{
            type: String | Number,
            default:0
        },
        loop:{
            type: Boolean,
            default: true
        },
        showIndicator:{
            type: Boolean,
            default: true
        },
        indicatorColor:{
            type: String
        },
        vertical:{
            type: Boolean,
            default: false
        },
        swipeable:{
            type:Boolean,
            default: true
        }
    },
    data(){
        return {
            rect: null,
            width: 0,
            height: 0,
            offset: 0,
            active: 0,
            swiping: false,
            touch: Touch.Create(),
            touchStartTime:-1,
            autoplayTimer:-1
        }
    },
    computed:{
        cls(){
            return namespace.derive()
        },
        trackCls(){
            return namespace.derive('track',{
                vertical: this.vertical
            })
        },
        trackStyle(){
            const style = {
                transitionDuration: `${this.swiping ? 0: this.duration}ms`,
                transform:`translate${this.vertical?'Y':'X'}(${this.offset}px)`
            }
            if(this.size){
                style[this.vertical ? 'height':'width'] = this.trackSize
            }
            return style
        },
        indicatorsCls(){
            return namespace.derive('indicators',{
                vertical:this.vertical
            })
        },
        activeIndicator(){
            return (this.active + this.count) % this.count
        },
        count(){
            return this.children.length
        },
        size(){
            return this[this.vertical ? 'height' : 'width']
        },
        delta(){
            return this.vertical ? this.touch.deltaY : this.touch.deltaX
        },
        minOffset(){
            if(this.rect){
                return  (this.vertical ? this.rect.height : this.rect.width) - this.size* this.count
            }
            return 0
        },
        maxCount(){
            return Math.ceil(Math.abs(this.minOffset) / this.size)
        },
        trackSize(){
            return this.size * this.count
        },
        isCorrectDirection(){
            return this.touch.direction === (this.vertical ? Direction.VER : Direction.HOR)
        }
    },
    watch:{
        defaultActive(val){
            this.init(val)
        },
        count(){
            this.init(this.active)
        },
        autoPlay(){
            this.startAutoplay()
        }
    },
    onMounted(){
        this.init()
    },
    onActivated(){
        this.init(this.active)
    },
    onDeactivated(){
        this.stopAutoplay()
    },
    onBeforeUnmount(){
        this.stopAutoplay()
    },
    methods:{
        onTouchStart(e){
            if(!this.swipeable){
                return
            }
            this.touch.start(e)
            this.touchStartTime = Date.now()
            this.stopAutoplay()
            this.correctPosition()
        },
        onTouchMove(e){
            if(this.swipeable && this.swiping){
                this.touch.move(e)

                if(this.isCorrectDirection){
                    DOMExtension.PreventDefault(e,true)
                    this.move(0, this.delta)
                }
            }
        },
        onTouchEnd(e){
            if(!this.swipeable || !this.swiping){
                return
            }
            const duration = Date.now() - this.touchStartTime
            const speed = this.delta / duration
            const shouldSwipe = Math.abs(speed) > 0.25 || Math.abs(this.delta) > (this.size / 2)
            if(shouldSwipe && this.isCorrectDirection){
                const offset = this.vertical ? this.touch.offsetY : this.touch.offsetX

                let pace = 0

                if(this.loop){
                    pace = offset > 0 ? (this.delta > 0 ? -1: 1) : 0
                }else {
                    pace = -Math[this.delta > 0 ?'ceil':'floor'](this.delta / this.size)
                }

                this.move(pace,0, true)
            }else if(this.delta){
                this.move(0)
            }
            this.swiping = false

            this.startAutoplay()
        },
        stopAutoplay(){
            clearTimeout(this.autoplayTimer)
        },
        startAutoplay(){
            this.stopAutoplay()
            if(this.autoplay > 0 && this.count > 1){
                this.autoplayTimer = setTimeout(()=>{
                    this.next()
                    this.startAutoplay()
                }, +this.autoplay)
            }
        },
        prev(){
            this.correctPosition()
            this.touch.reset()
            doubleRaf(()=>{
                this.swiping = false
                this.move(-1,0,true)
            })
        },
        next(){
            this.correctPosition()
            this.touch.reset()
            doubleRaf(()=>{
                this.swiping = false
                this.move(1,0,true)
            })
        },
        swipeTo(index, options){
            this.correctPosition()
            this.touch.reset()

            doubleRaf(()=>{
                let targetIndex;
                if(this.loop && index === this.count){
                    targetIndex = this.active === 0 ? 0 : index
                }else {
                    targetIndex = index % this.count
                }

                if(options.immediate){
                    doubleRaf(()=>{
                        this.swiping =false
                    })
                }else{
                    this.swiping = false
                }
                this.move(targetIndex - this.active,0, true)
            })
        },
        move(pace=0,offset=0,emitChange=false){
            if(this.count <= 1){
                return
            }
            const {active} = this.$data
            const targetActive = this.getTargetActive(pace)
            const targetOffset = this.getTargetOffset(targetActive, offset)

            if(this.loop){
                if(this.children[0] && targetOffset !== this.minOffset){
                    this.children[0].setOffset(targetOffset < this.minOffset ? this.trackSize : 0)
                }
                if(this.children[this.count - 1] && targetOffset !== 0){
                    this.children[this.count - 1].setOffset(targetOffset > 0 ? -this.trackSize : 0 )
                }
            }

            this.active = targetActive
            this.offset = targetOffset

            if(emitChange && active !== targetActive ){
                this.$emit('change',this.activeIndicator)
            }
        },
        correctPosition(){
            this.swiping = true
            if(this.active <= -1){
                this.move(this.count)
            }else if(this.active >= this.count){
                this.move(-this.count)
            }
        },
        getTargetActive(pace){
            const {active} = this.$data
            if(pace){
                if(this.loop){
                    return MathExtension.Clamp(active + pace, -1, this.count)
                }
                return MathExtension.Clamp(active + pace, 0, this.maxCount)
            }
            return active
        },
        getTargetOffset(targetActive,offset=0){
            let currentPosition = targetActive * this.size
            if(!this.loop){
                currentPosition = Math.min(currentPosition, this.minOffset)
            }
            let targetOffset = offset - currentPosition
            if(!this.loop){
                targetOffset = MathExtension.Clamp(targetOffset, this.minOffset, 0)
            }
            return targetOffset
        },
        isHide(el){
            if(!el){
                return false
            }
            const style = window.getComputedStyle(el);
            const hidden = style.display === 'none';
          
            const parentHidden = el.offsetParent === null && style.position !== 'fixed';
          
            return hidden || parentHidden;
        },
        init(active = this.defaultActive){
            if(!this.$el){
                return
            }
            if(!this.isHide(this.$el)){
                const rect = {
                    width: this.$el.offsetWidth,
                    height: this.$el.offsetHeight
                }
                this.rect = rect
                this.width =  rect.width
                this.height = rect.height
            }

            if(this.count){
                active = Math.min(this.count-1, active)
            }

            this.active = active
            this.swiping = true
            this.offset = this.getTargetOffset(active)
            this.children.forEach(child=>child.setOffset(0))
            this.startAutoplay()
        },
        resize(){
            this.init(this.active)
        }
    },
    render(){
        const renderDot = (_,index)=>{
            const active = index === this.activeIndicator
            const style = {}
            if(active){
                style.backgroundColor = this.indicatorColor
            }
            return <i style ={style} class={namespace.derive('indicator',{active})} />
        }
        const renderIndicator = ()=>{
            if(this.$scopedSlots.indicator){
                return this.$scopedSlots.indicator({active: this.activeIndicator})
            }
            if(this.showIndicator && this.count > 1){
                return (
                    <div class={this.indicatorsCls}>
                        {Array(this.count).fill(null).map(renderDot)}
                    </div>
                )
            }
        }
        return (
            <div class={this.cls}>
                <div class={this.trackCls} 
                    style={this.trackStyle}
                    onTouchstart={this.onTouchStart}
                    onTouchmove={this.onTouchMove}
                    onTouchend={this.onTouchEnd}
                    onTouchcancel={this.onTouchEnd}
                >
                    {this.$slots.default}
                </div>
                {renderIndicator()}
            </div>
        )
    }
}