import { ReactiveControllerHost, ReactiveController } from "lit";
import { LitDragStart, LitDragging, LitDragEnd } from "./lit-events.js";

export type DragBoundaries = {
    top: number;
    left: number;
    bottom: number;
    right: number;
};

export type DragOptions = {
    preventSelect: boolean;
};

export type DragAxes = "x" | "y" | "both" | "none";

export type DragBounds = HTMLElement | Partial<DragBoundaries> | "parent" | "body" | (string & Record<never, never>);

const defaultOptions: DragOptions = {
    preventSelect: true,
};

export class LitDraggable implements ReactiveController {
    host: ReactiveControllerHost & HTMLElement;

    options: DragOptions;

    translateX = 0;
    translateY = 0;

    initialX = 0;
    initialY = 0;

    handleOffsetX = 0;
    handleOffsetY = 0;

    dragging = false;

    currentSelect?: string;

    pointers = new Set<number>();

    constructor(host: ReactiveControllerHost & HTMLElement, options: DragOptions = defaultOptions) {
        (this.host = host).addController(this);
        this.options = options;
        this.dragStart = this.dragStart.bind(this);
        this.drag = this.drag.bind(this);
        this.dragEnd = this.dragEnd.bind(this);
    }

    // TODO: Make this work with a handle, which will be a `ref` object.
    hostConnected() {
        console.log(this.host);
        this.host.addEventListener("pointerdown", this.dragStart);
    }

    hostDisconnected() {
        this.host.removeEventListener("pointerdown", this.dragStart);
    }

    dragStart(ev: PointerEvent) {
        if (ev.button === 2) {
            return;
        }

        document.addEventListener("pointerup", this.dragEnd);
        document.addEventListener("pointermove", this.drag);

        const event_target = ev.composedPath()[0] as HTMLElement;
        if (!(event_target === this.host || this.host.shadowRoot?.contains(event_target))) {
            console.log("HUH?");
            return;
        }

        this.pointers.add(ev.pointerId);
        this.dragging = true;

        if (this.options.preventSelect) {
            this.currentSelect = document.body.style.userSelect;
            document.body.style.userSelect = "none";
        }

        const hostRect = this.host.getBoundingClientRect();

        this.initialX = ev.clientX;
        this.initialY = ev.clientY;
        this.handleOffsetX = this.initialX - hostRect.left;
        this.handleOffsetY = this.initialY - hostRect.top;
        this.host.dataset.litDrag = "true";
        this.host.dispatchEvent(new LitDragStart(this));
    }

    drag(ev: PointerEvent) {
        if (!this.dragging || !this.pointers.has(ev.pointerId)) {
            return;
        }
        this.host.dataset.litDragging = "true";
        ev.preventDefault();

        this.translateX = ev.clientX - this.initialX;
        this.translateY = ev.clientY - this.initialY;
        this.host.dispatchEvent(new LitDragging(this));
        this.host.style.setProperty("transform", `translate3d(${+this.translateX}px, ${+this.translateY}px, 0)`);
    }

    dragEnd(ev: PointerEvent) {
        document.removeEventListener("pointerup", this.dragEnd);
        document.removeEventListener("pointermove", this.drag);

        if (!this.dragging || !this.pointers.has(ev.pointerId)) {
            return;
        }
        this.pointers.delete(ev.pointerId);
        this.dragging = false;

        delete this.host.dataset.litDrag;
        delete this.host.dataset.litDragging;
        if (this.currentSelect) {
            document.body.style.userSelect = this.currentSelect;
            this.currentSelect = undefined;
        }

        this.initialX = ev.clientX - this.handleOffsetX;
        this.initialY = ev.clientY - this.handleOffsetY;
        this.host.dispatchEvent(new LitDragEnd(this));
        this.host.style.removeProperty("transform");
    }
}
