import { ref, unref, watch } from 'vue';
import type { Ref } from 'vue';
import { isObject } from './utils/index';
import type { DOM, DragCore, DragCoreOption, EventOption } from './index';
import { drag } from './index';

type MaybeRef<T> = T | Ref<T>;

/** 元素拖拽 */
export function vDragsFunc(option: VDragCoreOption) {
    const dragIns = ref<ReturnType<typeof drag> | null>(null);
    /** 初始化拖拽 */
    function init() {
        destroy();
        // if (option.disabled) return;
        dragIns.value = drag({ ...option, disabled: unref(option.disabled) });
        return destroy;
    }
    /** 销毁拖拽 */
    function destroy() {
        dragIns.value?.destroyed();
        dragIns.value = null;
    }
    watch(() => [option.target, option.handle, option.disabled], init, { immediate: true });
    return { init, destroy, dragIns };
}

export interface Binding {
    value: BindingValue;
    modifiers: {
        /** 拖拽时只应用虚拟坐标, 元素坐标不更新 */
        virtualAxis?: boolean;
        /** 是否限制范围 */
        boundaryLimit?: boolean;
        /** 是否吸边 */
        snap?: boolean;
        /** 是否强制吸边 */
        forceSnap?: boolean;
        /** 靠近滚动条边缘时, 是否自动滚动 */
        scrolling?: boolean;
        /** 是否需要虚影跟随 */
        shadowFollow?: boolean;
        /** 虚影跟随时, 是否使用固定定位 */
        shadowFollowFixed?: boolean;
    };
}
export type BindingValue = DOM | VDragCoreOption;
export interface VDragCoreOption extends Omit<DragCoreOption, 'disabled'> {
    /** 是否禁用拖拽 */
    disabled?: MaybeRef<boolean>;
}

/**
 * 拖拽指令
 * @param {object} modifiers 修饰符的参数定义如下
 * @param {boolean} [modifiers.virtualAxis] 拖拽时只应用虚拟坐标, 元素坐标不更新
 * @param {boolean} [modifiers.boundaryLimit] 元素拖动不能超出范围
 * @param {boolean} [modifiers.snap] 是否开启边缘吸附效果, 未开启时, 吸边相关配置项皆不生效
 * @param {boolean} [modifiers.forceSnap] 是否强制边缘吸附效果
 * @param {boolean} [modifiers.scrolling] 存在滚动条时, 靠近边缘自动触发滚动效果
 * @param {boolean} [modifiers.shadowFollow] 是否克隆元素并跟随鼠标移动
 * @param {boolean} [modifiers.shadowFollowFixed] 是否为固定定位元素
 *
 * @emit('axisBeforeUpdate', option: Omit<EventOption, 'native'>, ins: DragCore): 坐标更新前的回调事件 - 可直接修改坐标值来覆盖实际坐标
 * @emit('axisUpdated', option: Omit<EventOption, 'native'>, ins: DragCore): 坐标更新后的回调事件
 * @param {Omit<EventOption, 'native'>} option 更新的坐标信息
 * @param {boolean} option.dragging 是否处于拖拽中
 * @param {number} option.x 坐标
 * @param {number} option.y 坐标
 * @param {DragCore} ins 拖拽实例
 */
export const draggable = {
    /** 获取拖拽所需的参数 */
    getOptions(binding: Binding, el: HTMLElement) {
        const params: VDragCoreOption = { target: el };
        Object.keys(binding.modifiers).forEach((k) => {
            setOptionByAttr[k as keyof typeof setOptionByAttr] ? setOptionByAttr[k as keyof typeof setOptionByAttr](params) : params[k as 'virtualAxis'] = true;
        });
        if (binding.value) {
            if (isObject(binding.value)) {
                const { target, handle, ...args } = binding.value as Exclude<typeof binding.value, DOM>;
                target && (params.target = target);
                handle && (params.handle = handle);
                Object.assign(params, args);
            }
            else {
                params.handle = binding.value;
            }
        }
        return params;
    },
    /** vue3.0+ 自定义指令 */
    mounted(el: HTMLElement, binding: Binding, vnode: any) {
        const params = draggable.getOptions(binding, el);
        const drags = vDragsFunc(params);
        // @ts-expect-error 将事件挂载到元素上
        el.drags = drags;
    },
    /** vue3.0+ 自定义指令 */
    updated(el: HTMLElement, binding: Binding, vnode: any, prevVnode: any) {
        // @ts-expect-error 获取挂载到元素上拖拽实例
        if (!el.drags) el.drags = prevVnode.el?.drags;
        // @ts-expect-error 获取挂载到元素上拖拽实例
        if (!el.drags?.dragIns?.value) {
            prevVnode.el?.drags?.destroy?.();
            draggable.mounted(el, binding, vnode);
        }
        else {
            // @ts-expect-error 将事件挂载到元素上
            const dragIns = el.drags.dragIns.value as DragCore;
            const params = draggable.getOptions(binding, el);
            if (params.target === dragIns.option.target || params.handle === dragIns.option.handle) {
                delete params.target;
                delete params.handle;
            }
            params.disabled !== !dragIns.status && (params.disabled ? dragIns.disabled() : dragIns.enabled());
            dragIns.updateOption(params);
        }
    },
    /** vue3.0+ 自定义指令 */
    beforeUnmount(el: HTMLElement) {
        // @ts-expect-error 获取挂载到元素上拖拽实例
        el.drags?.destroy?.();
    },

    /** vue2.0+ 自定义指令 */
    bind(el: HTMLElement, binding: Binding, vnode: any) {
        return draggable.mounted(el, binding, vnode);
    },
    /** vue2.0+ 自定义指令 */
    componentUpdated(el: HTMLElement, binding: Binding, vnode: any, prevVnode: any) {
        return draggable.updated(el, binding, vnode, prevVnode);
    },
    /** vue2.0+ 自定义指令 */
    unbind(el: HTMLElement) {
        return draggable.beforeUnmount(el);
    },
};

const setOptionByAttr = {
    boundaryLimit: (obj: Record<string, any>) => {
        if (!obj.boundaryLimitOptions) obj.boundaryLimitOptions = {};
    },
    snap: (obj: Record<string, any>) => {
        if (!obj.snapOptions) obj.snapOptions = {};
    },
    forceSnap: (obj: Record<string, any>) => {
        if (!obj.snapOptions) obj.snapOptions = {};
        obj.snapOptions.forceSnap = true;
    },
    scrolling: (obj: Record<string, any>) => {
        if (!obj.scrollingOptions) obj.scrollingOptions = {};
    },
    shadowFollow: (obj: Record<string, any>) => {
        if (!obj.shadowFollowOptions) obj.shadowFollowOptions = {};
    },
    shadowFollowFixed: (obj: Record<string, any>) => {
        if (!obj.shadowFollowOptions) obj.shadowFollowOptions = {};
        obj.shadowFollowOptions.fixed = true;
    },
};
