// Module: drag.js
// Uses the built-in HTML support for dragging and dropping elements.
// When an element is draggable, its ID is saved as data for the drag
// When dropped, the ID is passed to the drop handler.
//
// Since we are using jQuery, we pass data to the handler using the data
// property of jQuery Events.
//
// For the drop handler we use a variable at top module level to pass the dragged element.

import $ from "./jquery.module.js";

let $draggedElement = null;

let clampDrag = null;

const blankImage = new Image();
blankImage.src = 'data:image/gif;base64,R0lGODlhAQABAIAAAAUEBAAAACwAAAAAAQABAAACAkQBADs=';

function clamp(x, min, max) {
    return x < min ? min : (x > max ? max : x);
}

function makeDraggable({element, start, stop, moveElement, disablePointerEventsOnDrag}) {
    // Optional arguments:
    //      start: handler     -- If defined, a handler to be called for the event "dragstart"
    //      stop: handler,     -- If defined, a handler to be called for the event "dragend"
    //      moveElement: true, -- If defined and true, allows the mouse to move the element while dragging.
    //                            In this case, the body is set as droppable to prevent the drag operation
    //                            from snapping back when finished.
    const $element = element instanceof $ ? element : $(element);
    $element.attr("draggable", "true");
    $element.on("dragstart", {$element, start, moveElement, disablePointerEventsOnDrag}, startDrag);
    $element.on("dragend", {$element, stop, disablePointerEventsOnDrag}, endDrag);
}

function startDrag(event) {
    let {$element, start, moveElement, disablePointerEventsOnDrag} = event.data;
    console.log("Called dragstart");
    event.originalEvent.dataTransfer.setData("text/plain", this.id);
    $draggedElement = event.data.$element;

    if (start) {
        start(event);
    }

    if (moveElement) {
        event.originalEvent.dataTransfer.setDragImage(blankImage, 0, 0);
        startMoveElement(event, {disablePointerEventsOnDrag});
        // makeDroppable({element: "body", dropHandler: () => {}}); // Make body droppable but don't do anything
    }

}

function endDrag(event) {
    console.log("Called endDrag");
    let {$element, stop, disablePointerEventsOnDrag} = event.data;
    if (disablePointerEventsOnDrag) {
        // If we called startMoveElement then pointer-events were turned off while dragging
        $element.css("pointer-events", "all");
    }
    stop?.()
}

function makeDroppable({element, dropHandler}) {
    const $element = element instanceof $ ? element : $(element);
    $element
        .on("dragover", (evt) => evt.preventDefault())
        .on("drop", function(evt) {
            evt.preventDefault();
            dropHandler({$draggedElement, $dropElement: $element});
            $draggedElement = null;
        });
}

function clampMotion({xMin, xMax, yMin, yMax}) {
    clampDrag = {xMin, xMax, yMin, yMax};
}

function startMoveElement(evt) {
    console.log("Called startMoveElement");
    let {disablePointerEventsOnDrag} = evt.data;
    let $el = $(evt.currentTarget);
    let offset = {x: parseFloat($el.css("left")) - evt.pageX, y: parseFloat($el.css("top")) - evt.pageY};
    $el.on("drag", {offset, disablePointerEventsOnDrag}, moveElement);
}

function moveElement(evt) {
    // For some reason we get called one more time when the mouse is released with pageX and pageY set to zero, so we
    // have to check for that

    let pageX = evt.pageX;
    let pageY = evt.pageY;
    if (pageX === 0 && pageY === 0) {
        return;
    }
    let $el = $(evt.currentTarget);
    let {offset, disablePointerEventsOnDrag} = evt.data;
    let horiz = offset.x + pageX;
    let vert  = offset.y + pageY;
    let width = parseFloat($el.css("width"));
    let height = parseFloat($el.css("height"));
    if (clampDrag) {
        horiz = clamp(horiz, clampDrag.xMin, clampDrag.xMax - width);
        vert  = clamp(vert,  clampDrag.yMin, clampDrag.yMax - height);
        console.log("Clamping ", horiz, vert);
    }

    let theCSS = {
        left: horiz,
        top: vert,
    };

    if (disablePointerEventsOnDrag && $el.css("pointer-events") !== "none") {
        // Note: in the following, pointer events are disabled to prevent the
        // element being moved from blocking a drop target. If this isn't done,
        // drop targets will not see a dragenter event.

        theCSS["pointer-events"] = "none";
    }

    $el.css(theCSS);
}

export {makeDraggable, makeDroppable, clampMotion};