"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const dom_util_1 = require("@mablhq-internal/mablscript-find/dom-util");
const domUtils_1 = require("../../sharedUtils/domUtils");
const actions_1 = require("../actions");
const domTools_1 = require("../domTools");
const BaseContentEventHandler_1 = require("./BaseContentEventHandler");
const util_1 = require("./util");
const walkthrough_1 = require("../../sharedUtils/walkthrough");
class MouseClickEventHandler extends BaseContentEventHandler_1.BaseContentEventHandler {
    constructor(mouseHoverEventHandler, store, selectorCache) {
        super(undefined, 'Mouse Click Event Handler', store, selectorCache);
        this.mouseHoverEventHandler = mouseHoverEventHandler;
        this.handlePointerEvent = this.handlePointerEvent.bind(this);
        this.handleMouseDownEvent = this.handleMouseDownEvent.bind(this);
        this.handlePointerMoveEvent = this.handlePointerMoveEvent.bind(this);
        this.handleDragStartEvent = this.handleDragStartEvent.bind(this);
        this.handleDragOverEvent = this.handleDragOverEvent.bind(this);
        this.handleDragEndEvent = this.handleDragEndEvent.bind(this);
        this.resetPointerEventInfo();
    }
    register() {
        window.addEventListener('pointerdown', this.handlePointerEvent, true);
        window.addEventListener('pointerdown', this.handleMouseDownEvent, true);
        window.addEventListener('pointermove', this.handlePointerMoveEvent, true);
        window.addEventListener('pointerup', this.handlePointerEvent, true);
        window.addEventListener('dragstart', this.handleDragStartEvent, true);
        window.addEventListener('dragover', this.handleDragOverEvent, true);
        window.addEventListener('drop', this.handleDragOverEvent, true);
        window.addEventListener('dragend', this.handleDragEndEvent, true);
    }
    unregister() {
        window.removeEventListener('pointerdown', this.handlePointerEvent, true);
        window.removeEventListener('pointermove', this.handlePointerMoveEvent, true);
        window.removeEventListener('pointerdown', this.handleMouseDownEvent, true);
        window.removeEventListener('pointerup', this.handlePointerEvent, true);
        window.removeEventListener('dragstart', this.handleDragStartEvent, true);
        window.removeEventListener('dragover', this.handleDragOverEvent, true);
        window.removeEventListener('drop', this.handleDragOverEvent, true);
        window.removeEventListener('dragend', this.handleDragEndEvent, true);
    }
    handleMouseDownEvent() {
        (0, actions_1.sendSnapshotMessage)();
    }
    async handlePointerEvent(event) {
        if (!(0, util_1.shouldListenForTrainedStepInRecording)(this.store.getState()) ||
            !(0, util_1.isTrustedEvent)(event)) {
            return;
        }
        const { path, target } = (0, domUtils_1.adjustTarget)(event.target, (0, util_1.getPathOnEvent)(event));
        if (!target || this.shouldIgnoreClickOn(target)) {
            return;
        }
        const isInWalkthrough = this.store.getState().walkthrough.isInProgress;
        if (isInWalkthrough) {
            const isInTrainerContext = this.store.getState().walkthrough.activeContext ===
                walkthrough_1.ActiveWalkthroughContext.TRAINER;
            if (isInTrainerContext || !(0, util_1.isWalkthroughTarget)(target)) {
                return;
            }
        }
        const xPath = (0, dom_util_1.createXpathFromPathArray)(path);
        if (event.type === 'pointerdown') {
            this.isPointerDown = true;
            await this.storePointerDownInfo(event, target, xPath, false);
            return;
        }
        if (this.isValidPointerClickAndDrag(event)) {
            await this.handleDragEndEvent(event);
            this.resetPointerEventInfo();
            return;
        }
        const downTimestamp = this.pointerDownInfo.sourceEventTimeStamp ?? 0;
        if (event.type === 'pointerup' &&
            (xPath === this.pointerDownInfo.xPath ||
                event.timeStamp - downTimestamp < 350.0)) {
            await this.recordClickStep(event);
            this.resetPointerEventInfo();
        }
    }
    async handlePointerMoveEvent(event) {
        const isInWalkthrough = this.store.getState().walkthrough.isInProgress;
        if (!this.isPointerDown ||
            !(0, util_1.shouldListenForTrainedStepInRecording)(this.store.getState()) ||
            !(0, util_1.isTrustedEvent)(event) ||
            isInWalkthrough) {
            return;
        }
        const target = this.findPotentialDropTargetByEventCoordinates(event, this.pointerDownInfo?.target);
        if (target && target !== this.pointerUpInfo.target) {
            const xPath = (0, dom_util_1.createXPathFromElement)(target);
            await this.storePointerUpInfo(event, target, xPath, false);
        }
    }
    async handleDragStartEvent(event) {
        const isInWalkthrough = this.store.getState().walkthrough.isInProgress;
        if (!(0, util_1.shouldListenForTrainedStepInRecording)(this.store.getState()) ||
            !(0, util_1.isTrustedEvent)(event) ||
            isInWalkthrough) {
            return;
        }
        const { path, target } = (0, domUtils_1.adjustTarget)(event.target, (0, util_1.getPathOnEvent)(event));
        if (!target) {
            return;
        }
        const xPath = (0, dom_util_1.createXpathFromPathArray)(path);
        await this.storePointerDownInfo(event, target, xPath, true);
        this.pointerUpInfo = {};
    }
    async handleDragOverEvent(event) {
        const isInWalkthrough = this.store.getState().walkthrough.isInProgress;
        if (!(0, util_1.shouldListenForTrainedStepInRecording)(this.store.getState()) ||
            !(0, util_1.isTrustedEvent)(event) ||
            isInWalkthrough) {
            return;
        }
        const { path, target } = (0, domUtils_1.adjustTarget)(event.target, (0, util_1.getPathOnEvent)(event));
        if (!target ||
            target === this.pointerDownInfo.target ||
            target === this.pointerUpInfo.target) {
            return;
        }
        const xPath = (0, dom_util_1.createXpathFromPathArray)(path);
        await this.storePointerUpInfo(event, target, xPath, true);
    }
    async handleDragEndEvent(event) {
        const isInWalkthrough = this.store.getState().walkthrough.isInProgress;
        if (!(0, util_1.shouldListenForTrainedStepInRecording)(this.store.getState()) ||
            !(0, util_1.isTrustedEvent)(event) ||
            isInWalkthrough) {
            return;
        }
        if (this.pointerDownInfo.findOneClickAndHoldEventMessage &&
            this.pointerUpInfo.findOneReleaseEventMessage) {
            void (0, actions_1.sendEventMessage)(this.pointerDownInfo.findOneClickAndHoldEventMessage);
            void (0, actions_1.sendEventMessage)(this.pointerUpInfo.findOneReleaseEventMessage);
        }
        else {
            const draggedElement = this.pointerDownInfo.target;
            let dropTarget = this.pointerUpInfo.target;
            if (!dropTarget && draggedElement) {
                dropTarget = this.findPotentialDropTargetByEventCoordinates(event, draggedElement);
            }
            if (draggedElement && dropTarget) {
                const dropTargetXPath = this.pointerUpInfo.xPath || (0, dom_util_1.createXPathFromElement)(dropTarget);
                const draggedElementXPath = this.pointerDownInfo.xPath || (0, dom_util_1.createXPathFromElement)(draggedElement);
                await this.recordDragAndDropStep(draggedElement, draggedElementXPath, dropTarget, dropTargetXPath, !!this.pointerUpInfo.isHtml5, event.timeStamp);
            }
            else if (draggedElement) {
                const draggedElementXPath = this.pointerDownInfo.xPath || (0, dom_util_1.createXPathFromElement)(draggedElement);
                await this.recordDragAndDropStep(draggedElement, draggedElementXPath, draggedElement, draggedElementXPath, !!this.pointerUpInfo.isHtml5, event.timeStamp);
            }
        }
        this.resetPointerEventInfo();
    }
    async recordClickStep(event) {
        if (!this.pointerDownInfo.target) {
            throw new Error('No target from pointer down event to use in event creation');
        }
        if (this.pointerDownInfo.xPath === undefined) {
            throw new Error('No xpath from pointer down event to use in event creation');
        }
        if (this.pointerDownInfo.sourceEventTimeStamp === undefined) {
            throw new Error('No sourceEventTimeStamp from pointer down event to use in event creation');
        }
        const actionListenerName = event.button === 2 ? 'right-click' : 'click';
        this.mouseHoverEventHandler.recordHovers();
        const clickEventMessage = await (0, domTools_1.createEventMessageWithShadowParents)(this.pointerDownInfo.target, 'record', actionListenerName, this.pointerDownInfo.xPath, this.pointerDownInfo.sourceEventTimeStamp);
        void (0, actions_1.sendEventMessage)(clickEventMessage);
    }
    async recordDragAndDropStep(draggedElement, draggedElementXPath, dropTarget, dropTargetXPath, isHtml5, timeStamp) {
        const findOneClickAndHoldEventMessage = await (0, domTools_1.createEventMessageWithShadowParents)(draggedElement, 'record', 'click_hold', draggedElementXPath, timeStamp);
        findOneClickAndHoldEventMessage.isHtml5 = isHtml5;
        void (0, actions_1.sendEventMessage)(findOneClickAndHoldEventMessage);
        const releaseBaseEventMessage = await (0, domTools_1.createEventMessageWithShadowParents)(dropTarget, 'record', 'release', dropTargetXPath, timeStamp);
        const findOneReleaseEventMessage = {
            ...releaseBaseEventMessage,
            releaseArgs: {
                isHtml5,
            },
        };
        void (0, actions_1.sendEventMessage)(findOneReleaseEventMessage);
    }
    shouldIgnoreClickOn(target) {
        return (0, domUtils_1.isSelect)(target) || (0, domUtils_1.isFileInput)(target);
    }
    async storePointerDownInfo(event, target, xPath, isHtml5) {
        const findOneClickAndHoldEventMessage = await (0, domTools_1.createEventMessageWithShadowParents)(target, 'record', 'click_hold', xPath, event.timeStamp);
        findOneClickAndHoldEventMessage.isHtml5 = isHtml5;
        this.pointerDownInfo = {
            findOneClickAndHoldEventMessage,
            isHtml5,
            target,
            sourceEventTimeStamp: event.timeStamp,
            xPath,
            x: event.x,
            y: event.y,
            event,
        };
    }
    async storePointerUpInfo(event, target, xPath, isHtml5) {
        const baseReleaseEventMessage = await (0, domTools_1.createEventMessageWithShadowParents)(target, 'record', 'release', xPath, event.timeStamp);
        const findOneReleaseEventMessage = {
            ...baseReleaseEventMessage,
            releaseArgs: { isHtml5 },
        };
        this.pointerUpInfo = {
            findOneReleaseEventMessage,
            isHtml5,
            target,
            sourceEventTimeStamp: event.timeStamp,
            xPath,
            x: event.x,
            y: event.y,
        };
    }
    resetPointerEventInfo() {
        this.pointerDownInfo = {};
        this.pointerUpInfo = {};
        this.isPointerDown = false;
    }
    isValidPointerClickAndDrag(event) {
        if (event.type !== 'pointerup') {
            return false;
        }
        const minPixelDistance = 20;
        const minDragTimeMs = 500;
        const pixelDistance = Math.sqrt(Math.pow(event.pageX - (this.pointerDownInfo?.x ?? 0), 2) +
            Math.pow(event.pageY - (this.pointerDownInfo?.y ?? 0), 2));
        return (event.timeStamp - (this.pointerDownInfo.sourceEventTimeStamp ?? 0) >
            minDragTimeMs && pixelDistance > minPixelDistance);
    }
    findPotentialDropTargetByEventCoordinates(event, draggedElement) {
        if (!draggedElement) {
            return;
        }
        const elementsOnPoint = document.elementsFromPoint(event.x, event.y);
        let currElement = draggedElement;
        const draggedElementAncestors = [];
        while (currElement) {
            draggedElementAncestors.push(currElement);
            currElement = currElement.parentElement;
        }
        return elementsOnPoint.find((element) => !draggedElementAncestors.includes(element) &&
            element instanceof HTMLElement);
    }
}
exports.default = MouseClickEventHandler;
