/*---------------------------------------------------------------------------------------------
 *  Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/
var __extends = (this && this.__extends) || (function () {
    var extendStatics = function (d, b) {
        extendStatics = Object.setPrototypeOf ||
            ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
            function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; };
        return extendStatics(d, b);
    };
    return function (d, b) {
        extendStatics(d, b);
        function __() { this.constructor = d; }
        d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
    };
})();
import * as browser from '../../../base/browser/browser';
import { PageCoordinates } from '../editorDom';
import { PartFingerprints } from '../view/viewPart';
import { ViewLine } from '../viewParts/lines/viewLine';
import { Position } from '../../common/core/position';
import { Range as EditorRange } from '../../common/core/range';
import { CursorColumns } from '../../common/controller/cursorCommon';
import * as dom from '../../../base/browser/dom';
var PointerHandlerLastRenderData = /** @class */ (function () {
    function PointerHandlerLastRenderData(lastViewCursorsRenderData, lastTextareaPosition) {
        this.lastViewCursorsRenderData = lastViewCursorsRenderData;
        this.lastTextareaPosition = lastTextareaPosition;
    }
    return PointerHandlerLastRenderData;
}());
export { PointerHandlerLastRenderData };
var MouseTarget = /** @class */ (function () {
    function MouseTarget(element, type, mouseColumn, position, range, detail) {
        if (mouseColumn === void 0) { mouseColumn = 0; }
        if (position === void 0) { position = null; }
        if (range === void 0) { range = null; }
        if (detail === void 0) { detail = null; }
        this.element = element;
        this.type = type;
        this.mouseColumn = mouseColumn;
        this.position = position;
        if (!range && position) {
            range = new EditorRange(position.lineNumber, position.column, position.lineNumber, position.column);
        }
        this.range = range;
        this.detail = detail;
    }
    MouseTarget._typeToString = function (type) {
        if (type === 1 /* TEXTAREA */) {
            return 'TEXTAREA';
        }
        if (type === 2 /* GUTTER_GLYPH_MARGIN */) {
            return 'GUTTER_GLYPH_MARGIN';
        }
        if (type === 3 /* GUTTER_LINE_NUMBERS */) {
            return 'GUTTER_LINE_NUMBERS';
        }
        if (type === 4 /* GUTTER_LINE_DECORATIONS */) {
            return 'GUTTER_LINE_DECORATIONS';
        }
        if (type === 5 /* GUTTER_VIEW_ZONE */) {
            return 'GUTTER_VIEW_ZONE';
        }
        if (type === 6 /* CONTENT_TEXT */) {
            return 'CONTENT_TEXT';
        }
        if (type === 7 /* CONTENT_EMPTY */) {
            return 'CONTENT_EMPTY';
        }
        if (type === 8 /* CONTENT_VIEW_ZONE */) {
            return 'CONTENT_VIEW_ZONE';
        }
        if (type === 9 /* CONTENT_WIDGET */) {
            return 'CONTENT_WIDGET';
        }
        if (type === 10 /* OVERVIEW_RULER */) {
            return 'OVERVIEW_RULER';
        }
        if (type === 11 /* SCROLLBAR */) {
            return 'SCROLLBAR';
        }
        if (type === 12 /* OVERLAY_WIDGET */) {
            return 'OVERLAY_WIDGET';
        }
        return 'UNKNOWN';
    };
    MouseTarget.toString = function (target) {
        return this._typeToString(target.type) + ': ' + target.position + ' - ' + target.range + ' - ' + target.detail;
    };
    MouseTarget.prototype.toString = function () {
        return MouseTarget.toString(this);
    };
    return MouseTarget;
}());
export { MouseTarget };
var ElementPath = /** @class */ (function () {
    function ElementPath() {
    }
    ElementPath.isTextArea = function (path) {
        return (path.length === 2
            && path[0] === 3 /* OverflowGuard */
            && path[1] === 6 /* TextArea */);
    };
    ElementPath.isChildOfViewLines = function (path) {
        return (path.length >= 4
            && path[0] === 3 /* OverflowGuard */
            && path[3] === 7 /* ViewLines */);
    };
    ElementPath.isStrictChildOfViewLines = function (path) {
        return (path.length > 4
            && path[0] === 3 /* OverflowGuard */
            && path[3] === 7 /* ViewLines */);
    };
    ElementPath.isChildOfScrollableElement = function (path) {
        return (path.length >= 2
            && path[0] === 3 /* OverflowGuard */
            && path[1] === 5 /* ScrollableElement */);
    };
    ElementPath.isChildOfMinimap = function (path) {
        return (path.length >= 2
            && path[0] === 3 /* OverflowGuard */
            && path[1] === 8 /* Minimap */);
    };
    ElementPath.isChildOfContentWidgets = function (path) {
        return (path.length >= 4
            && path[0] === 3 /* OverflowGuard */
            && path[3] === 1 /* ContentWidgets */);
    };
    ElementPath.isChildOfOverflowingContentWidgets = function (path) {
        return (path.length >= 1
            && path[0] === 2 /* OverflowingContentWidgets */);
    };
    ElementPath.isChildOfOverlayWidgets = function (path) {
        return (path.length >= 2
            && path[0] === 3 /* OverflowGuard */
            && path[1] === 4 /* OverlayWidgets */);
    };
    return ElementPath;
}());
var HitTestContext = /** @class */ (function () {
    function HitTestContext(context, viewHelper, lastRenderData) {
        this.model = context.model;
        var options = context.configuration.options;
        this.layoutInfo = options.get(107 /* layoutInfo */);
        this.viewDomNode = viewHelper.viewDomNode;
        this.lineHeight = options.get(49 /* lineHeight */);
        this.typicalHalfwidthCharacterWidth = options.get(34 /* fontInfo */).typicalHalfwidthCharacterWidth;
        this.lastRenderData = lastRenderData;
        this._context = context;
        this._viewHelper = viewHelper;
    }
    HitTestContext.prototype.getZoneAtCoord = function (mouseVerticalOffset) {
        return HitTestContext.getZoneAtCoord(this._context, mouseVerticalOffset);
    };
    HitTestContext.getZoneAtCoord = function (context, mouseVerticalOffset) {
        // The target is either a view zone or the empty space after the last view-line
        var viewZoneWhitespace = context.viewLayout.getWhitespaceAtVerticalOffset(mouseVerticalOffset);
        if (viewZoneWhitespace) {
            var viewZoneMiddle = viewZoneWhitespace.verticalOffset + viewZoneWhitespace.height / 2, lineCount = context.model.getLineCount(), positionBefore = null, position = void 0, positionAfter = null;
            if (viewZoneWhitespace.afterLineNumber !== lineCount) {
                // There are more lines after this view zone
                positionAfter = new Position(viewZoneWhitespace.afterLineNumber + 1, 1);
            }
            if (viewZoneWhitespace.afterLineNumber > 0) {
                // There are more lines above this view zone
                positionBefore = new Position(viewZoneWhitespace.afterLineNumber, context.model.getLineMaxColumn(viewZoneWhitespace.afterLineNumber));
            }
            if (positionAfter === null) {
                position = positionBefore;
            }
            else if (positionBefore === null) {
                position = positionAfter;
            }
            else if (mouseVerticalOffset < viewZoneMiddle) {
                position = positionBefore;
            }
            else {
                position = positionAfter;
            }
            return {
                viewZoneId: viewZoneWhitespace.id,
                afterLineNumber: viewZoneWhitespace.afterLineNumber,
                positionBefore: positionBefore,
                positionAfter: positionAfter,
                position: position
            };
        }
        return null;
    };
    HitTestContext.prototype.getFullLineRangeAtCoord = function (mouseVerticalOffset) {
        if (this._context.viewLayout.isAfterLines(mouseVerticalOffset)) {
            // Below the last line
            var lineNumber_1 = this._context.model.getLineCount();
            var maxLineColumn_1 = this._context.model.getLineMaxColumn(lineNumber_1);
            return {
                range: new EditorRange(lineNumber_1, maxLineColumn_1, lineNumber_1, maxLineColumn_1),
                isAfterLines: true
            };
        }
        var lineNumber = this._context.viewLayout.getLineNumberAtVerticalOffset(mouseVerticalOffset);
        var maxLineColumn = this._context.model.getLineMaxColumn(lineNumber);
        return {
            range: new EditorRange(lineNumber, 1, lineNumber, maxLineColumn),
            isAfterLines: false
        };
    };
    HitTestContext.prototype.getLineNumberAtVerticalOffset = function (mouseVerticalOffset) {
        return this._context.viewLayout.getLineNumberAtVerticalOffset(mouseVerticalOffset);
    };
    HitTestContext.prototype.isAfterLines = function (mouseVerticalOffset) {
        return this._context.viewLayout.isAfterLines(mouseVerticalOffset);
    };
    HitTestContext.prototype.getVerticalOffsetForLineNumber = function (lineNumber) {
        return this._context.viewLayout.getVerticalOffsetForLineNumber(lineNumber);
    };
    HitTestContext.prototype.findAttribute = function (element, attr) {
        return HitTestContext._findAttribute(element, attr, this._viewHelper.viewDomNode);
    };
    HitTestContext._findAttribute = function (element, attr, stopAt) {
        while (element && element !== document.body) {
            if (element.hasAttribute && element.hasAttribute(attr)) {
                return element.getAttribute(attr);
            }
            if (element === stopAt) {
                return null;
            }
            element = element.parentNode;
        }
        return null;
    };
    HitTestContext.prototype.getLineWidth = function (lineNumber) {
        return this._viewHelper.getLineWidth(lineNumber);
    };
    HitTestContext.prototype.visibleRangeForPosition = function (lineNumber, column) {
        return this._viewHelper.visibleRangeForPosition(lineNumber, column);
    };
    HitTestContext.prototype.getPositionFromDOMInfo = function (spanNode, offset) {
        return this._viewHelper.getPositionFromDOMInfo(spanNode, offset);
    };
    HitTestContext.prototype.getCurrentScrollTop = function () {
        return this._context.viewLayout.getCurrentScrollTop();
    };
    HitTestContext.prototype.getCurrentScrollLeft = function () {
        return this._context.viewLayout.getCurrentScrollLeft();
    };
    return HitTestContext;
}());
export { HitTestContext };
var BareHitTestRequest = /** @class */ (function () {
    function BareHitTestRequest(ctx, editorPos, pos) {
        this.editorPos = editorPos;
        this.pos = pos;
        this.mouseVerticalOffset = Math.max(0, ctx.getCurrentScrollTop() + pos.y - editorPos.y);
        this.mouseContentHorizontalOffset = ctx.getCurrentScrollLeft() + pos.x - editorPos.x - ctx.layoutInfo.contentLeft;
        this.isInMarginArea = (pos.x - editorPos.x < ctx.layoutInfo.contentLeft && pos.x - editorPos.x >= ctx.layoutInfo.glyphMarginLeft);
        this.isInContentArea = !this.isInMarginArea;
        this.mouseColumn = Math.max(0, MouseTargetFactory._getMouseColumn(this.mouseContentHorizontalOffset, ctx.typicalHalfwidthCharacterWidth));
    }
    return BareHitTestRequest;
}());
var HitTestRequest = /** @class */ (function (_super) {
    __extends(HitTestRequest, _super);
    function HitTestRequest(ctx, editorPos, pos, target) {
        var _this = _super.call(this, ctx, editorPos, pos) || this;
        _this._ctx = ctx;
        if (target) {
            _this.target = target;
            _this.targetPath = PartFingerprints.collect(target, ctx.viewDomNode);
        }
        else {
            _this.target = null;
            _this.targetPath = new Uint8Array(0);
        }
        return _this;
    }
    HitTestRequest.prototype.toString = function () {
        return "pos(" + this.pos.x + "," + this.pos.y + "), editorPos(" + this.editorPos.x + "," + this.editorPos.y + "), mouseVerticalOffset: " + this.mouseVerticalOffset + ", mouseContentHorizontalOffset: " + this.mouseContentHorizontalOffset + "\n\ttarget: " + (this.target ? this.target.outerHTML : null);
    };
    HitTestRequest.prototype.fulfill = function (type, position, range, detail) {
        if (position === void 0) { position = null; }
        if (range === void 0) { range = null; }
        if (detail === void 0) { detail = null; }
        var mouseColumn = this.mouseColumn;
        if (position && position.column < this._ctx.model.getLineMaxColumn(position.lineNumber)) {
            // Most likely, the line contains foreign decorations...
            mouseColumn = CursorColumns.visibleColumnFromColumn(this._ctx.model.getLineContent(position.lineNumber), position.column, this._ctx.model.getOptions().tabSize) + 1;
        }
        return new MouseTarget(this.target, type, mouseColumn, position, range, detail);
    };
    HitTestRequest.prototype.withTarget = function (target) {
        return new HitTestRequest(this._ctx, this.editorPos, this.pos, target);
    };
    return HitTestRequest;
}(BareHitTestRequest));
var EMPTY_CONTENT_AFTER_LINES = { isAfterLines: true };
function createEmptyContentDataInLines(horizontalDistanceToText) {
    return {
        isAfterLines: false,
        horizontalDistanceToText: horizontalDistanceToText
    };
}
var MouseTargetFactory = /** @class */ (function () {
    function MouseTargetFactory(context, viewHelper) {
        this._context = context;
        this._viewHelper = viewHelper;
    }
    MouseTargetFactory.prototype.mouseTargetIsWidget = function (e) {
        var t = e.target;
        var path = PartFingerprints.collect(t, this._viewHelper.viewDomNode);
        // Is it a content widget?
        if (ElementPath.isChildOfContentWidgets(path) || ElementPath.isChildOfOverflowingContentWidgets(path)) {
            return true;
        }
        // Is it an overlay widget?
        if (ElementPath.isChildOfOverlayWidgets(path)) {
            return true;
        }
        return false;
    };
    MouseTargetFactory.prototype.createMouseTarget = function (lastRenderData, editorPos, pos, target) {
        var ctx = new HitTestContext(this._context, this._viewHelper, lastRenderData);
        var request = new HitTestRequest(ctx, editorPos, pos, target);
        try {
            var r = MouseTargetFactory._createMouseTarget(ctx, request, false);
            // console.log(r.toString());
            return r;
        }
        catch (err) {
            // console.log(err);
            return request.fulfill(0 /* UNKNOWN */);
        }
    };
    MouseTargetFactory._createMouseTarget = function (ctx, request, domHitTestExecuted) {
        // console.log(`${domHitTestExecuted ? '=>' : ''}CAME IN REQUEST: ${request}`);
        // First ensure the request has a target
        if (request.target === null) {
            if (domHitTestExecuted) {
                // Still no target... and we have already executed hit test...
                return request.fulfill(0 /* UNKNOWN */);
            }
            var hitTestResult = MouseTargetFactory._doHitTest(ctx, request);
            if (hitTestResult.position) {
                return MouseTargetFactory.createMouseTargetFromHitTestPosition(ctx, request, hitTestResult.position.lineNumber, hitTestResult.position.column);
            }
            return this._createMouseTarget(ctx, request.withTarget(hitTestResult.hitTarget), true);
        }
        // we know for a fact that request.target is not null
        var resolvedRequest = request;
        var result = null;
        result = result || MouseTargetFactory._hitTestContentWidget(ctx, resolvedRequest);
        result = result || MouseTargetFactory._hitTestOverlayWidget(ctx, resolvedRequest);
        result = result || MouseTargetFactory._hitTestMinimap(ctx, resolvedRequest);
        result = result || MouseTargetFactory._hitTestScrollbarSlider(ctx, resolvedRequest);
        result = result || MouseTargetFactory._hitTestViewZone(ctx, resolvedRequest);
        result = result || MouseTargetFactory._hitTestMargin(ctx, resolvedRequest);
        result = result || MouseTargetFactory._hitTestViewCursor(ctx, resolvedRequest);
        result = result || MouseTargetFactory._hitTestTextArea(ctx, resolvedRequest);
        result = result || MouseTargetFactory._hitTestViewLines(ctx, resolvedRequest, domHitTestExecuted);
        result = result || MouseTargetFactory._hitTestScrollbar(ctx, resolvedRequest);
        return (result || request.fulfill(0 /* UNKNOWN */));
    };
    MouseTargetFactory._hitTestContentWidget = function (ctx, request) {
        // Is it a content widget?
        if (ElementPath.isChildOfContentWidgets(request.targetPath) || ElementPath.isChildOfOverflowingContentWidgets(request.targetPath)) {
            var widgetId = ctx.findAttribute(request.target, 'widgetId');
            if (widgetId) {
                return request.fulfill(9 /* CONTENT_WIDGET */, null, null, widgetId);
            }
            else {
                return request.fulfill(0 /* UNKNOWN */);
            }
        }
        return null;
    };
    MouseTargetFactory._hitTestOverlayWidget = function (ctx, request) {
        // Is it an overlay widget?
        if (ElementPath.isChildOfOverlayWidgets(request.targetPath)) {
            var widgetId = ctx.findAttribute(request.target, 'widgetId');
            if (widgetId) {
                return request.fulfill(12 /* OVERLAY_WIDGET */, null, null, widgetId);
            }
            else {
                return request.fulfill(0 /* UNKNOWN */);
            }
        }
        return null;
    };
    MouseTargetFactory._hitTestViewCursor = function (ctx, request) {
        if (request.target) {
            // Check if we've hit a painted cursor
            var lastViewCursorsRenderData = ctx.lastRenderData.lastViewCursorsRenderData;
            for (var _i = 0, lastViewCursorsRenderData_1 = lastViewCursorsRenderData; _i < lastViewCursorsRenderData_1.length; _i++) {
                var d = lastViewCursorsRenderData_1[_i];
                if (request.target === d.domNode) {
                    return request.fulfill(6 /* CONTENT_TEXT */, d.position);
                }
            }
        }
        if (request.isInContentArea) {
            // Edge has a bug when hit-testing the exact position of a cursor,
            // instead of returning the correct dom node, it returns the
            // first or last rendered view line dom node, therefore help it out
            // and first check if we are on top of a cursor
            var lastViewCursorsRenderData = ctx.lastRenderData.lastViewCursorsRenderData;
            var mouseContentHorizontalOffset = request.mouseContentHorizontalOffset;
            var mouseVerticalOffset = request.mouseVerticalOffset;
            for (var _a = 0, lastViewCursorsRenderData_2 = lastViewCursorsRenderData; _a < lastViewCursorsRenderData_2.length; _a++) {
                var d = lastViewCursorsRenderData_2[_a];
                if (mouseContentHorizontalOffset < d.contentLeft) {
                    // mouse position is to the left of the cursor
                    continue;
                }
                if (mouseContentHorizontalOffset > d.contentLeft + d.width) {
                    // mouse position is to the right of the cursor
                    continue;
                }
                var cursorVerticalOffset = ctx.getVerticalOffsetForLineNumber(d.position.lineNumber);
                if (cursorVerticalOffset <= mouseVerticalOffset
                    && mouseVerticalOffset <= cursorVerticalOffset + d.height) {
                    return request.fulfill(6 /* CONTENT_TEXT */, d.position);
                }
            }
        }
        return null;
    };
    MouseTargetFactory._hitTestViewZone = function (ctx, request) {
        var viewZoneData = ctx.getZoneAtCoord(request.mouseVerticalOffset);
        if (viewZoneData) {
            var mouseTargetType = (request.isInContentArea ? 8 /* CONTENT_VIEW_ZONE */ : 5 /* GUTTER_VIEW_ZONE */);
            return request.fulfill(mouseTargetType, viewZoneData.position, null, viewZoneData);
        }
        return null;
    };
    MouseTargetFactory._hitTestTextArea = function (ctx, request) {
        // Is it the textarea?
        if (ElementPath.isTextArea(request.targetPath)) {
            if (ctx.lastRenderData.lastTextareaPosition) {
                return request.fulfill(6 /* CONTENT_TEXT */, ctx.lastRenderData.lastTextareaPosition);
            }
            return request.fulfill(1 /* TEXTAREA */, ctx.lastRenderData.lastTextareaPosition);
        }
        return null;
    };
    MouseTargetFactory._hitTestMargin = function (ctx, request) {
        if (request.isInMarginArea) {
            var res = ctx.getFullLineRangeAtCoord(request.mouseVerticalOffset);
            var pos = res.range.getStartPosition();
            var offset = Math.abs(request.pos.x - request.editorPos.x);
            var detail = {
                isAfterLines: res.isAfterLines,
                glyphMarginLeft: ctx.layoutInfo.glyphMarginLeft,
                glyphMarginWidth: ctx.layoutInfo.glyphMarginWidth,
                lineNumbersWidth: ctx.layoutInfo.lineNumbersWidth,
                offsetX: offset
            };
            offset -= ctx.layoutInfo.glyphMarginLeft;
            if (offset <= ctx.layoutInfo.glyphMarginWidth) {
                // On the glyph margin
                return request.fulfill(2 /* GUTTER_GLYPH_MARGIN */, pos, res.range, detail);
            }
            offset -= ctx.layoutInfo.glyphMarginWidth;
            if (offset <= ctx.layoutInfo.lineNumbersWidth) {
                // On the line numbers
                return request.fulfill(3 /* GUTTER_LINE_NUMBERS */, pos, res.range, detail);
            }
            offset -= ctx.layoutInfo.lineNumbersWidth;
            // On the line decorations
            return request.fulfill(4 /* GUTTER_LINE_DECORATIONS */, pos, res.range, detail);
        }
        return null;
    };
    MouseTargetFactory._hitTestViewLines = function (ctx, request, domHitTestExecuted) {
        if (!ElementPath.isChildOfViewLines(request.targetPath)) {
            return null;
        }
        // Check if it is below any lines and any view zones
        if (ctx.isAfterLines(request.mouseVerticalOffset)) {
            // This most likely indicates it happened after the last view-line
            var lineCount = ctx.model.getLineCount();
            var maxLineColumn = ctx.model.getLineMaxColumn(lineCount);
            return request.fulfill(7 /* CONTENT_EMPTY */, new Position(lineCount, maxLineColumn), undefined, EMPTY_CONTENT_AFTER_LINES);
        }
        if (domHitTestExecuted) {
            // Check if we are hitting a view-line (can happen in the case of inline decorations on empty lines)
            // See https://github.com/Microsoft/vscode/issues/46942
            if (ElementPath.isStrictChildOfViewLines(request.targetPath)) {
                var lineNumber = ctx.getLineNumberAtVerticalOffset(request.mouseVerticalOffset);
                if (ctx.model.getLineLength(lineNumber) === 0) {
                    var lineWidth_1 = ctx.getLineWidth(lineNumber);
                    var detail = createEmptyContentDataInLines(request.mouseContentHorizontalOffset - lineWidth_1);
                    return request.fulfill(7 /* CONTENT_EMPTY */, new Position(lineNumber, 1), undefined, detail);
                }
                var lineWidth = ctx.getLineWidth(lineNumber);
                if (request.mouseContentHorizontalOffset >= lineWidth) {
                    var detail = createEmptyContentDataInLines(request.mouseContentHorizontalOffset - lineWidth);
                    var pos = new Position(lineNumber, ctx.model.getLineMaxColumn(lineNumber));
                    return request.fulfill(7 /* CONTENT_EMPTY */, pos, undefined, detail);
                }
            }
            // We have already executed hit test...
            return request.fulfill(0 /* UNKNOWN */);
        }
        var hitTestResult = MouseTargetFactory._doHitTest(ctx, request);
        if (hitTestResult.position) {
            return MouseTargetFactory.createMouseTargetFromHitTestPosition(ctx, request, hitTestResult.position.lineNumber, hitTestResult.position.column);
        }
        return this._createMouseTarget(ctx, request.withTarget(hitTestResult.hitTarget), true);
    };
    MouseTargetFactory._hitTestMinimap = function (ctx, request) {
        if (ElementPath.isChildOfMinimap(request.targetPath)) {
            var possibleLineNumber = ctx.getLineNumberAtVerticalOffset(request.mouseVerticalOffset);
            var maxColumn = ctx.model.getLineMaxColumn(possibleLineNumber);
            return request.fulfill(11 /* SCROLLBAR */, new Position(possibleLineNumber, maxColumn));
        }
        return null;
    };
    MouseTargetFactory._hitTestScrollbarSlider = function (ctx, request) {
        if (ElementPath.isChildOfScrollableElement(request.targetPath)) {
            if (request.target && request.target.nodeType === 1) {
                var className = request.target.className;
                if (className && /\b(slider|scrollbar)\b/.test(className)) {
                    var possibleLineNumber = ctx.getLineNumberAtVerticalOffset(request.mouseVerticalOffset);
                    var maxColumn = ctx.model.getLineMaxColumn(possibleLineNumber);
                    return request.fulfill(11 /* SCROLLBAR */, new Position(possibleLineNumber, maxColumn));
                }
            }
        }
        return null;
    };
    MouseTargetFactory._hitTestScrollbar = function (ctx, request) {
        // Is it the overview ruler?
        // Is it a child of the scrollable element?
        if (ElementPath.isChildOfScrollableElement(request.targetPath)) {
            var possibleLineNumber = ctx.getLineNumberAtVerticalOffset(request.mouseVerticalOffset);
            var maxColumn = ctx.model.getLineMaxColumn(possibleLineNumber);
            return request.fulfill(11 /* SCROLLBAR */, new Position(possibleLineNumber, maxColumn));
        }
        return null;
    };
    MouseTargetFactory.prototype.getMouseColumn = function (editorPos, pos) {
        var options = this._context.configuration.options;
        var layoutInfo = options.get(107 /* layoutInfo */);
        var mouseContentHorizontalOffset = this._context.viewLayout.getCurrentScrollLeft() + pos.x - editorPos.x - layoutInfo.contentLeft;
        return MouseTargetFactory._getMouseColumn(mouseContentHorizontalOffset, options.get(34 /* fontInfo */).typicalHalfwidthCharacterWidth);
    };
    MouseTargetFactory._getMouseColumn = function (mouseContentHorizontalOffset, typicalHalfwidthCharacterWidth) {
        if (mouseContentHorizontalOffset < 0) {
            return 1;
        }
        var chars = Math.round(mouseContentHorizontalOffset / typicalHalfwidthCharacterWidth);
        return (chars + 1);
    };
    MouseTargetFactory.createMouseTargetFromHitTestPosition = function (ctx, request, lineNumber, column) {
        var pos = new Position(lineNumber, column);
        var lineWidth = ctx.getLineWidth(lineNumber);
        if (request.mouseContentHorizontalOffset > lineWidth) {
            if (browser.isEdge && pos.column === 1) {
                // See https://github.com/Microsoft/vscode/issues/10875
                var detail_1 = createEmptyContentDataInLines(request.mouseContentHorizontalOffset - lineWidth);
                return request.fulfill(7 /* CONTENT_EMPTY */, new Position(lineNumber, ctx.model.getLineMaxColumn(lineNumber)), undefined, detail_1);
            }
            var detail = createEmptyContentDataInLines(request.mouseContentHorizontalOffset - lineWidth);
            return request.fulfill(7 /* CONTENT_EMPTY */, pos, undefined, detail);
        }
        var visibleRange = ctx.visibleRangeForPosition(lineNumber, column);
        if (!visibleRange) {
            return request.fulfill(0 /* UNKNOWN */, pos);
        }
        var columnHorizontalOffset = visibleRange.left;
        if (request.mouseContentHorizontalOffset === columnHorizontalOffset) {
            return request.fulfill(6 /* CONTENT_TEXT */, pos);
        }
        var points = [];
        points.push({ offset: visibleRange.left, column: column });
        if (column > 1) {
            var visibleRange_1 = ctx.visibleRangeForPosition(lineNumber, column - 1);
            if (visibleRange_1) {
                points.push({ offset: visibleRange_1.left, column: column - 1 });
            }
        }
        var lineMaxColumn = ctx.model.getLineMaxColumn(lineNumber);
        if (column < lineMaxColumn) {
            var visibleRange_2 = ctx.visibleRangeForPosition(lineNumber, column + 1);
            if (visibleRange_2) {
                points.push({ offset: visibleRange_2.left, column: column + 1 });
            }
        }
        points.sort(function (a, b) { return a.offset - b.offset; });
        for (var i = 1; i < points.length; i++) {
            var prev = points[i - 1];
            var curr = points[i];
            if (prev.offset <= request.mouseContentHorizontalOffset && request.mouseContentHorizontalOffset <= curr.offset) {
                var rng = new EditorRange(lineNumber, prev.column, lineNumber, curr.column);
                return request.fulfill(6 /* CONTENT_TEXT */, pos, rng);
            }
        }
        return request.fulfill(6 /* CONTENT_TEXT */, pos);
    };
    /**
     * Most probably WebKit browsers and Edge
     */
    MouseTargetFactory._doHitTestWithCaretRangeFromPoint = function (ctx, request) {
        // In Chrome, especially on Linux it is possible to click between lines,
        // so try to adjust the `hity` below so that it lands in the center of a line
        var lineNumber = ctx.getLineNumberAtVerticalOffset(request.mouseVerticalOffset);
        var lineVerticalOffset = ctx.getVerticalOffsetForLineNumber(lineNumber);
        var lineCenteredVerticalOffset = lineVerticalOffset + Math.floor(ctx.lineHeight / 2);
        var adjustedPageY = request.pos.y + (lineCenteredVerticalOffset - request.mouseVerticalOffset);
        if (adjustedPageY <= request.editorPos.y) {
            adjustedPageY = request.editorPos.y + 1;
        }
        if (adjustedPageY >= request.editorPos.y + ctx.layoutInfo.height) {
            adjustedPageY = request.editorPos.y + ctx.layoutInfo.height - 1;
        }
        var adjustedPage = new PageCoordinates(request.pos.x, adjustedPageY);
        var r = this._actualDoHitTestWithCaretRangeFromPoint(ctx, adjustedPage.toClientCoordinates());
        if (r.position) {
            return r;
        }
        // Also try to hit test without the adjustment (for the edge cases that we are near the top or bottom)
        return this._actualDoHitTestWithCaretRangeFromPoint(ctx, request.pos.toClientCoordinates());
    };
    MouseTargetFactory._actualDoHitTestWithCaretRangeFromPoint = function (ctx, coords) {
        var shadowRoot = dom.getShadowRoot(ctx.viewDomNode);
        var range;
        if (shadowRoot) {
            if (typeof shadowRoot.caretRangeFromPoint === 'undefined') {
                range = shadowCaretRangeFromPoint(shadowRoot, coords.clientX, coords.clientY);
            }
            else {
                range = shadowRoot.caretRangeFromPoint(coords.clientX, coords.clientY);
            }
        }
        else {
            range = document.caretRangeFromPoint(coords.clientX, coords.clientY);
        }
        if (!range || !range.startContainer) {
            return {
                position: null,
                hitTarget: null
            };
        }
        // Chrome always hits a TEXT_NODE, while Edge sometimes hits a token span
        var startContainer = range.startContainer;
        var hitTarget = null;
        if (startContainer.nodeType === startContainer.TEXT_NODE) {
            // startContainer is expected to be the token text
            var parent1 = startContainer.parentNode; // expected to be the token span
            var parent2 = parent1 ? parent1.parentNode : null; // expected to be the view line container span
            var parent3 = parent2 ? parent2.parentNode : null; // expected to be the view line div
            var parent3ClassName = parent3 && parent3.nodeType === parent3.ELEMENT_NODE ? parent3.className : null;
            if (parent3ClassName === ViewLine.CLASS_NAME) {
                var p = ctx.getPositionFromDOMInfo(parent1, range.startOffset);
                return {
                    position: p,
                    hitTarget: null
                };
            }
            else {
                hitTarget = startContainer.parentNode;
            }
        }
        else if (startContainer.nodeType === startContainer.ELEMENT_NODE) {
            // startContainer is expected to be the token span
            var parent1 = startContainer.parentNode; // expected to be the view line container span
            var parent2 = parent1 ? parent1.parentNode : null; // expected to be the view line div
            var parent2ClassName = parent2 && parent2.nodeType === parent2.ELEMENT_NODE ? parent2.className : null;
            if (parent2ClassName === ViewLine.CLASS_NAME) {
                var p = ctx.getPositionFromDOMInfo(startContainer, startContainer.textContent.length);
                return {
                    position: p,
                    hitTarget: null
                };
            }
            else {
                hitTarget = startContainer;
            }
        }
        return {
            position: null,
            hitTarget: hitTarget
        };
    };
    /**
     * Most probably Gecko
     */
    MouseTargetFactory._doHitTestWithCaretPositionFromPoint = function (ctx, coords) {
        var hitResult = document.caretPositionFromPoint(coords.clientX, coords.clientY);
        if (hitResult.offsetNode.nodeType === hitResult.offsetNode.TEXT_NODE) {
            // offsetNode is expected to be the token text
            var parent1 = hitResult.offsetNode.parentNode; // expected to be the token span
            var parent2 = parent1 ? parent1.parentNode : null; // expected to be the view line container span
            var parent3 = parent2 ? parent2.parentNode : null; // expected to be the view line div
            var parent3ClassName = parent3 && parent3.nodeType === parent3.ELEMENT_NODE ? parent3.className : null;
            if (parent3ClassName === ViewLine.CLASS_NAME) {
                var p = ctx.getPositionFromDOMInfo(hitResult.offsetNode.parentNode, hitResult.offset);
                return {
                    position: p,
                    hitTarget: null
                };
            }
            else {
                return {
                    position: null,
                    hitTarget: hitResult.offsetNode.parentNode
                };
            }
        }
        return {
            position: null,
            hitTarget: hitResult.offsetNode
        };
    };
    /**
     * Most probably IE
     */
    MouseTargetFactory._doHitTestWithMoveToPoint = function (ctx, coords) {
        var resultPosition = null;
        var resultHitTarget = null;
        var textRange = document.body.createTextRange();
        try {
            textRange.moveToPoint(coords.clientX, coords.clientY);
        }
        catch (err) {
            return {
                position: null,
                hitTarget: null
            };
        }
        textRange.collapse(true);
        // Now, let's do our best to figure out what we hit :)
        var parentElement = textRange ? textRange.parentElement() : null;
        var parent1 = parentElement ? parentElement.parentNode : null;
        var parent2 = parent1 ? parent1.parentNode : null;
        var parent2ClassName = parent2 && parent2.nodeType === parent2.ELEMENT_NODE ? parent2.className : '';
        if (parent2ClassName === ViewLine.CLASS_NAME) {
            var rangeToContainEntireSpan = textRange.duplicate();
            rangeToContainEntireSpan.moveToElementText(parentElement);
            rangeToContainEntireSpan.setEndPoint('EndToStart', textRange);
            resultPosition = ctx.getPositionFromDOMInfo(parentElement, rangeToContainEntireSpan.text.length);
            // Move range out of the span node, IE doesn't like having many ranges in
            // the same spot and will act badly for lines containing dashes ('-')
            rangeToContainEntireSpan.moveToElementText(ctx.viewDomNode);
        }
        else {
            // Looks like we've hit the hover or something foreign
            resultHitTarget = parentElement;
        }
        // Move range out of the span node, IE doesn't like having many ranges in
        // the same spot and will act badly for lines containing dashes ('-')
        textRange.moveToElementText(ctx.viewDomNode);
        return {
            position: resultPosition,
            hitTarget: resultHitTarget
        };
    };
    MouseTargetFactory._doHitTest = function (ctx, request) {
        // State of the art (18.10.2012):
        // The spec says browsers should support document.caretPositionFromPoint, but nobody implemented it (http://dev.w3.org/csswg/cssom-view/)
        // Gecko:
        //    - they tried to implement it once, but failed: https://bugzilla.mozilla.org/show_bug.cgi?id=654352
        //    - however, they do give out rangeParent/rangeOffset properties on mouse events
        // Webkit:
        //    - they have implemented a previous version of the spec which was using document.caretRangeFromPoint
        // IE:
        //    - they have a proprietary method on ranges, moveToPoint: https://msdn.microsoft.com/en-us/library/ie/ms536632(v=vs.85).aspx
        // 24.08.2016: Edge has added WebKit's document.caretRangeFromPoint, but it is quite buggy
        //    - when hit testing the cursor it returns the first or the last line in the viewport
        //    - it inconsistently hits text nodes or span nodes, while WebKit only hits text nodes
        //    - when toggling render whitespace on, and hit testing in the empty content after a line, it always hits offset 0 of the first span of the line
        // Thank you browsers for making this so 'easy' :)
        if (typeof document.caretRangeFromPoint === 'function') {
            return this._doHitTestWithCaretRangeFromPoint(ctx, request);
        }
        else if (document.caretPositionFromPoint) {
            return this._doHitTestWithCaretPositionFromPoint(ctx, request.pos.toClientCoordinates());
        }
        else if (document.body.createTextRange) {
            return this._doHitTestWithMoveToPoint(ctx, request.pos.toClientCoordinates());
        }
        return {
            position: null,
            hitTarget: null
        };
    };
    return MouseTargetFactory;
}());
export { MouseTargetFactory };
export function shadowCaretRangeFromPoint(shadowRoot, x, y) {
    var range = document.createRange();
    // Get the element under the point
    var el = shadowRoot.elementFromPoint(x, y);
    if (el !== null) {
        // Get the last child of the element until its firstChild is a text node
        // This assumes that the pointer is on the right of the line, out of the tokens
        // and that we want to get the offset of the last token of the line
        while (el && el.firstChild && el.firstChild.nodeType !== el.firstChild.TEXT_NODE) {
            el = el.lastChild;
        }
        // Grab its rect
        var rect = el.getBoundingClientRect();
        // And its font
        var font = window.getComputedStyle(el, null).getPropertyValue('font');
        // And also its txt content
        var text = el.innerText;
        // Position the pixel cursor at the left of the element
        var pixelCursor = rect.left;
        var offset = 0;
        var step = void 0;
        // If the point is on the right of the box put the cursor after the last character
        if (x > rect.left + rect.width) {
            offset = text.length;
        }
        else {
            var charWidthReader = CharWidthReader.getInstance();
            // Goes through all the characters of the innerText, and checks if the x of the point
            // belongs to the character.
            for (var i = 0; i < text.length + 1; i++) {
                // The step is half the width of the character
                step = charWidthReader.getCharWidth(text.charAt(i), font) / 2;
                // Move to the center of the character
                pixelCursor += step;
                // If the x of the point is smaller that the position of the cursor, the point is over that character
                if (x < pixelCursor) {
                    offset = i;
                    break;
                }
                // Move between the current character and the next
                pixelCursor += step;
            }
        }
        // Creates a range with the text node of the element and set the offset found
        range.setStart(el.firstChild, offset);
        range.setEnd(el.firstChild, offset);
    }
    return range;
}
var CharWidthReader = /** @class */ (function () {
    function CharWidthReader() {
        this._cache = {};
        this._canvas = document.createElement('canvas');
    }
    CharWidthReader.getInstance = function () {
        if (!CharWidthReader._INSTANCE) {
            CharWidthReader._INSTANCE = new CharWidthReader();
        }
        return CharWidthReader._INSTANCE;
    };
    CharWidthReader.prototype.getCharWidth = function (char, font) {
        var cacheKey = char + font;
        if (this._cache[cacheKey]) {
            return this._cache[cacheKey];
        }
        var context = this._canvas.getContext('2d');
        context.font = font;
        var metrics = context.measureText(char);
        var width = metrics.width;
        this._cache[cacheKey] = width;
        return width;
    };
    CharWidthReader._INSTANCE = null;
    return CharWidthReader;
}());
