import { ConnectionTargetResolver } from "../../connections/services/ConnectionTargetResolver.js";
import { NexusMarkerService } from "./NexusMarkerService.js";

const DRAG_THRESHOLD = 7;

export class NexusMarkerEntityDragController {
    static #state = null;

    static begin(placeable, originalEvent, { onClick = null } = {}) {
        if (Number(originalEvent?.button ?? 0) !== 0) return false;

        const record = NexusMarkerService.getMarkerRecord(placeable);
        const target = record?.target || {};
        if (target.kind !== "campaign-entity" || target.entityType !== "location" || !target.entityId) return false;

        const node = ConnectionTargetResolver.fromEntityReference({ entityId: target.entityId });
        if (!node || !ConnectionTargetResolver.canUserSeeNode(node)) return false;

        const point = this.#eventPoint(originalEvent);
        if (!point) return false;

        this.cancel();
        this.#state = {
            placeable,
            node,
            payload: {
                ...ConnectionTargetResolver.getDragData(node),
                augurNexusDragOrigin: "map-marker"
            },
            onClick,
            origin: point,
            point,
            started: false,
            preview: null,
            dataTransfer: null,
            hoverTarget: null,
            accepted: false
        };
        if (!this.#state.payload) {
            this.#state = null;
            return false;
        }

        this.#bindDocumentListeners();
        return true;
    }

    static cancel() {
        if (!this.#state) return;
        this.#leaveCurrentTarget();
        this.#removePreview();
        this.#state = null;
        this.#unbindDocumentListeners();
    }

    static #onMove = event => {
        const state = this.#state;
        if (!state) return;
        const point = this.#eventPoint(event);
        if (!point) return;
        state.point = point;

        if (!state.started) {
            const distance = Math.hypot(point.x - state.origin.x, point.y - state.origin.y);
            if (distance < DRAG_THRESHOLD) return;
            state.started = true;
            state.dataTransfer = this.#createDataTransfer(state.payload);
            this.#createPreview();
            document.body.classList.add("augur-nexus-marker-entity-dragging");
        }

        event.preventDefault?.();
        this.#positionPreview(point);
        this.#updateHoverTarget(point);
    };

    static #onUp = event => {
        const state = this.#state;
        if (!state) return;
        const point = this.#eventPoint(event) || state.point;

        if (!state.started) {
            const onClick = state.onClick;
            this.cancel();
            onClick?.();
            return;
        }

        event.preventDefault?.();
        event.stopPropagation?.();
        this.#positionPreview(point);
        this.#updateHoverTarget(point);
        if (state.accepted && state.hoverTarget) {
            this.#dispatchDragEvent("drop", state.hoverTarget, point);
        }
        this.cancel();
    };

    static #onCancel = () => this.cancel();

    static #onKeyDown = event => {
        if (event.key === "Escape") this.cancel();
    };

    static #bindDocumentListeners() {
        document.addEventListener("pointermove", this.#onMove, true);
        document.addEventListener("pointerup", this.#onUp, true);
        document.addEventListener("pointercancel", this.#onCancel, true);
        document.addEventListener("mousemove", this.#onMove, true);
        document.addEventListener("mouseup", this.#onUp, true);
        document.addEventListener("keydown", this.#onKeyDown, true);
        window.addEventListener("blur", this.#onCancel, true);
    }

    static #unbindDocumentListeners() {
        document.removeEventListener("pointermove", this.#onMove, true);
        document.removeEventListener("pointerup", this.#onUp, true);
        document.removeEventListener("pointercancel", this.#onCancel, true);
        document.removeEventListener("mousemove", this.#onMove, true);
        document.removeEventListener("mouseup", this.#onUp, true);
        document.removeEventListener("keydown", this.#onKeyDown, true);
        window.removeEventListener("blur", this.#onCancel, true);
    }

    static #updateHoverTarget(point) {
        const state = this.#state;
        if (!state?.started) return;
        const candidate = document.elementFromPoint(point.x, point.y);
        const nextTarget = this.#isCanvasSurface(candidate) ? null : candidate;
        if (nextTarget !== state.hoverTarget) {
            if (state.hoverTarget) this.#dispatchDragEvent("dragleave", state.hoverTarget, point, nextTarget);
            if (nextTarget) this.#dispatchDragEvent("dragenter", nextTarget, point, state.hoverTarget);
            state.hoverTarget = nextTarget;
        }
        state.accepted = !!nextTarget && this.#dispatchDragEvent("dragover", nextTarget, point) === false;
    }

    static #leaveCurrentTarget() {
        const state = this.#state;
        if (!state?.hoverTarget) return;
        this.#dispatchDragEvent("dragleave", state.hoverTarget, state.point);
        state.hoverTarget = null;
        state.accepted = false;
    }

    static #dispatchDragEvent(type, target, point, relatedTarget = null) {
        if (!target || !this.#state?.dataTransfer) return true;
        const init = {
            bubbles: true,
            cancelable: true,
            composed: true,
            clientX: point?.x || 0,
            clientY: point?.y || 0,
            relatedTarget,
            dataTransfer: this.#state.dataTransfer
        };
        let event;
        try {
            event = new DragEvent(type, init);
        } catch (_error) {
            event = new Event(type, { bubbles: true, cancelable: true, composed: true });
            for (const [key, value] of Object.entries(init)) {
                if (key === "bubbles" || key === "cancelable" || key === "composed") continue;
                Object.defineProperty(event, key, { configurable: true, value });
            }
        }
        return target.dispatchEvent(event);
    }

    static #createDataTransfer(payload) {
        let transfer;
        try {
            transfer = new DataTransfer();
        } catch (_error) {
            const values = new Map();
            transfer = {
                effectAllowed: "copy",
                dropEffect: "copy",
                setData: (type, value) => values.set(type, String(value)),
                getData: type => values.get(type) || "",
                clearData: type => type ? values.delete(type) : values.clear()
            };
        }
        const serialized = JSON.stringify(payload);
        transfer.setData("text/plain", serialized);
        transfer.setData("application/json", serialized);
        try { transfer.effectAllowed = "copy"; } catch (_error) { /* Browser-owned DataTransfer state may be read-only. */ }
        return transfer;
    }

    static #createPreview() {
        const state = this.#state;
        if (!state || state.preview) return;
        const preview = document.createElement("div");
        preview.className = "augur-nexus-marker-entity-drag-preview";

        if (state.node.img) {
            const image = document.createElement("img");
            image.src = state.node.img;
            image.alt = "";
            preview.append(image);
        } else {
            const icon = document.createElement("i");
            icon.className = "fas fa-location-dot";
            preview.append(icon);
        }

        const copy = document.createElement("span");
        const name = document.createElement("strong");
        const label = document.createElement("small");
        name.textContent = state.node.name || "Location";
        label.textContent = "Location";
        copy.append(name, label);
        preview.append(copy);
        document.body.append(preview);
        state.preview = preview;
        this.#positionPreview(state.point);
    }

    static #positionPreview(point) {
        const preview = this.#state?.preview;
        if (!preview || !point) return;
        preview.style.left = `${Math.round(point.x + 16)}px`;
        preview.style.top = `${Math.round(point.y + 16)}px`;
    }

    static #removePreview() {
        this.#state?.preview?.remove();
        if (this.#state) this.#state.preview = null;
        document.body.classList.remove("augur-nexus-marker-entity-dragging");
    }

    static #eventPoint(event) {
        const x = Number(event?.clientX);
        const y = Number(event?.clientY);
        if (!Number.isFinite(x) || !Number.isFinite(y)) return null;
        return { x, y };
    }

    static #isCanvasSurface(target) {
        const board = document.getElementById("board");
        return !!target && !!board && (target === board || board.contains(target));
    }
}
