import DragMoveFoundation, { DragMoveAdapter } from '@douyinfe/semi-foundation/dragMove/foundation';
import BaseComponent from '../_base/baseComponent';
import { ReactNode } from 'react';
import PropTypes from 'prop-types';
import React from 'react';
import { isHTMLElement } from '../_base/reactUtils';
import { resolveDOM, getRef } from '../_utils/reactRender';

export interface DragMoveProps {
    // The element that triggers the drag event，default is element
    handler?: () => HTMLElement;
    // The element that constrains the movement range, This element requires relative positioning
    constrainer?: () => HTMLElement | 'parent';
    children?: ReactNode | undefined | any;
    onMouseDown?: (e: MouseEvent) => void;
    onMouseMove?: (e: MouseEvent) => void;
    onMouseUp?: (e: MouseEvent) => void;
    onTouchStart?: (e: TouchEvent) => void;
    onTouchMove?: (e: TouchEvent) => void;
    onTouchEnd?: (e: TouchEvent) => void;
    onTouchCancel?: (e: TouchEvent) => void;
    // Determine whether dragging is triggered when the mouse is pressed. 
    // Return true to trigger dragging. Return false to not trigger dragging.
    allowMove?: (e: MouseEvent | TouchEvent, element: HTMLElement) => boolean;
    // customize move behavior
    customMove?: (e: HTMLElement, top: number, left: number) => void
}

export default class DragMove extends BaseComponent<DragMoveProps> {
    
    static propTypes = {
        children: PropTypes.node,
        handler: PropTypes.func,
        allowInputDrag: PropTypes.bool,
        constrainNode: PropTypes.func,
        constrainer: PropTypes.oneOfType([PropTypes.func, PropTypes.oneOf(['parent'])]),
        allowMove: PropTypes.func,
        customMove: PropTypes.func,
        onMouseDown: PropTypes.func,
        onMouseMove: PropTypes.func,
        onMouseUp: PropTypes.func,
        onTouchStart: PropTypes.func,
        onTouchMove: PropTypes.func,
        onTouchEnd: PropTypes.func,
        onTouchCancel: PropTypes.func,
    }

    static __SemiComponentName__ = "DragMove";

    static defaultProps = {
        allowInputDrag: false,
    };

    constructor(props: DragMoveProps) {
        super(props);
        this.elementRef = React.createRef();
        this.foundation = new DragMoveFoundation(this.adapter);
    }

    elementRef: React.RefObject<unknown>;
    foundation: DragMoveFoundation;

    get adapter(): DragMoveAdapter<DragMoveProps> {
        return {
            ...super.adapter,
            getDragElement: () => {
                let elementDom = this.elementRef.current;
                if (!isHTMLElement(elementDom)) {
                    elementDom = resolveDOM(elementDom);
                }
                return elementDom as HTMLElement;
            },
            getConstrainer: () => {
                const { constrainer } = this.props;
                if (typeof constrainer === 'string' && constrainer === 'parent') {
                    return (this.elementRef.current as HTMLElement)?.parentNode as HTMLElement;
                } else if (typeof constrainer === 'function') {
                    return constrainer() as any;
                } else {
                    return null;
                }
            },
            getHandler: () => {
                const { handler } = this.props;
                if (typeof handler === 'function') {
                    return handler() as any;
                } else {
                    return this.adapter.getDragElement() as HTMLElement;
                }
            },
            notifyMouseDown: (e: MouseEvent) => {
                this.props.onMouseDown && this.props.onMouseDown(e);
            },
            notifyMouseMove: (e: MouseEvent) => {
                this.props.onMouseMove && this.props.onMouseMove(e);
            },
            notifyMouseUp: (e: MouseEvent) => {
                this.props.onMouseUp && this.props.onMouseUp(e);
            },
            notifyTouchStart: (e: TouchEvent) => {
                this.props.onTouchStart && this.props.onTouchStart(e);
            },
            notifyTouchMove: (e: TouchEvent) => {
                this.props.onTouchMove && this.props.onTouchMove(e);
            },
            notifyTouchEnd: (e: TouchEvent) => {
                this.props.onTouchEnd && this.props.onTouchEnd(e);
            },
            notifyTouchCancel: (e: TouchEvent) => {
                this.props.onTouchCancel && this.props.onTouchCancel(e);
            },
        };
    }

    componentDidMount(): void {
        this.foundation.init();
    }

    componentWillUnmount(): void {
        this.foundation.destroy();
    }

    render() {
        const { children } = this.props;
        const newChildren = React.cloneElement(children, {
            ref: (node: React.ReactNode) => {
                (this.elementRef as any).current = node;
                const ref = getRef(children);
                if (typeof ref === 'function') {
                    ref(node);
                } else if (ref && typeof ref === 'object') {
                    (ref as React.MutableRefObject<any>).current = node;
                }
            },
        });
        return newChildren;
    }
}
 