"use strict";
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 (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; };
        return extendStatics(d, b);
    };
    return function (d, b) {
        if (typeof b !== "function" && b !== null)
            throw new TypeError("Class extends value " + String(b) + " is not a constructor or null");
        extendStatics(d, b);
        function __() { this.constructor = d; }
        d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
    };
})();
var __spreadArray = (this && this.__spreadArray) || function (to, from, pack) {
    if (pack || arguments.length === 2) for (var i = 0, l = from.length, ar; i < l; i++) {
        if (ar || !(i in from)) {
            if (!ar) ar = Array.prototype.slice.call(from, 0, i);
            ar[i] = from[i];
        }
    }
    return to.concat(ar || Array.prototype.slice.call(from));
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.SeriesSelectionModifier3D = void 0;
var classFactory_1 = require("../../Core/classFactory");
var EventHandler_1 = require("../../Core/EventHandler");
var Point_1 = require("../../Core/Point");
var array_1 = require("../../utils/array");
var BaseType_1 = require("../../types/BaseType");
var ExecuteOn_1 = require("../../types/ChartModifiers/ExecuteOn");
var ChartModifierType_1 = require("../../types/ChartModifierType");
var ChartModifierBase3D_1 = require("./ChartModifierBase3D");
var HoveredChangedArgs3D_1 = require("../Visuals/RenderableSeries/HoveredChangedArgs3D");
var SelectionChangedArgs3D_1 = require("../Visuals/RenderableSeries/SelectionChangedArgs3D");
/**
 * The SeriesSelectionModifier3D provides series selection and hover behavior on a 3D {@link SciChart3DSurface}
 */
var SeriesSelectionModifier3D = /** @class */ (function (_super) {
    __extends(SeriesSelectionModifier3D, _super);
    function SeriesSelectionModifier3D(options) {
        var _this = this;
        var _a, _b, _c, _d;
        _this = _super.call(this, options) || this;
        _this.type = ChartModifierType_1.EChart3DModifierType.SeriesSelection;
        /**
         * An array of currently selected series which can be observed by subscribing to the {@link selectionChanged} {@link EventHandler | event handler}
         */
        _this.selectedSeries = [];
        /**
         * An array of currently hovered series which can be observed by subscribing to the {@link hoverChanged} {@link EventHandler | event handler}
         */
        _this.hoveredSeries = [];
        /**
         * A selection-changed EventHandler. This is raised whenever any included series is selected or unselected.
         */
        _this.selectionChanged = new EventHandler_1.EventHandler();
        /**
         * A hover-changed EventHandler. This is raised whenever any included series is hovered or unhovered.
         */
        _this.hoverChanged = new EventHandler_1.EventHandler();
        _this.enableSelection = (_a = options === null || options === void 0 ? void 0 : options.enableSelection) !== null && _a !== void 0 ? _a : true;
        _this.enableHover = (_b = options === null || options === void 0 ? void 0 : options.enableHover) !== null && _b !== void 0 ? _b : false;
        _this.executeCondition = (_c = options === null || options === void 0 ? void 0 : options.executeCondition) !== null && _c !== void 0 ? _c : { button: ExecuteOn_1.EExecuteOn.MouseLeftButton };
        _this.hitTestRadius = _this.getHitTestRadius(options === null || options === void 0 ? void 0 : options.hitTestRadius);
        _this.prioritizeClosestToCamera = (_d = options === null || options === void 0 ? void 0 : options.prioritizeClosestToCamera) !== null && _d !== void 0 ? _d : false;
        _this.preventReentrancy = false;
        if (options === null || options === void 0 ? void 0 : options.onSelectionChanged) {
            if (typeof options.onSelectionChanged === "string") {
                _this.typeMap.set("onSelectionChanged", options.onSelectionChanged);
                var onSelectionChangedCallback = (0, classFactory_1.getFunction)(BaseType_1.EBaseType.OptionFunction, options.onSelectionChanged);
                _this.selectionChanged.subscribe(onSelectionChangedCallback);
            }
            else {
                _this.selectionChanged.subscribe(options.onSelectionChanged);
            }
        }
        if (options === null || options === void 0 ? void 0 : options.onHoverChanged) {
            if (typeof options.onHoverChanged === "string") {
                _this.typeMap.set("onHoverChanged", options.onHoverChanged);
                var onHoverChangedCallback = (0, classFactory_1.getFunction)(BaseType_1.EBaseType.OptionFunction, options.onHoverChanged);
                _this.hoverChanged.subscribe(onHoverChangedCallback);
            }
            else {
                _this.hoverChanged.subscribe(options.onHoverChanged);
            }
        }
        _this.updateSeriesSelected = _this.updateSeriesSelected.bind(_this);
        _this.updateSeriesHovered = _this.updateSeriesHovered.bind(_this);
        _this.onRenderableSeriesCollectionChanged = _this.onRenderableSeriesCollectionChanged.bind(_this);
        return _this;
    }
    /** @inheritDoc */
    SeriesSelectionModifier3D.prototype.onAttach = function () {
        var _this = this;
        var _a;
        _super.prototype.onAttach.call(this);
        this.selectedSeries = [];
        this.hoveredSeries = [];
        if (this.parentSurface) {
            this.parentSurface.isHitTestEnabled = true;
            this.parentSurface.renderableSeries.collectionChanged.subscribe(this.onRenderableSeriesCollectionChanged);
        }
        (_a = this.getIncludedRenderableSeries()) === null || _a === void 0 ? void 0 : _a.forEach(function (rs) { return _this.onAttachSeries(rs); });
    };
    /** @inheritDoc */
    SeriesSelectionModifier3D.prototype.onDetach = function () {
        var _this = this;
        var _a, _b;
        (_a = this.parentSurface) === null || _a === void 0 ? void 0 : _a.renderableSeries.asArray().forEach(function (rs) { return _this.onDetachSeries(rs); });
        this.selectedSeries = [];
        this.hoveredSeries = [];
        (_b = this.parentSurface) === null || _b === void 0 ? void 0 : _b.renderableSeries.collectionChanged.unsubscribe(this.onRenderableSeriesCollectionChanged);
        _super.prototype.onDetach.call(this);
    };
    /** @inheritDoc */
    SeriesSelectionModifier3D.prototype.onAttachSeries = function (rs) {
        var series = rs;
        if (!series) {
            return;
        }
        if (series.isSelected && !this.selectedSeries.includes(series)) {
            this.selectedSeries.push(series);
        }
        series.selected.unsubscribe(this.updateSeriesSelected);
        series.selected.subscribe(this.updateSeriesSelected);
        if (series.isHovered && !this.hoveredSeries.includes(series)) {
            this.hoveredSeries.push(series);
        }
        series.hovered.unsubscribe(this.updateSeriesHovered);
        series.hovered.subscribe(this.updateSeriesHovered);
    };
    /** @inheritDoc */
    SeriesSelectionModifier3D.prototype.onDetachSeries = function (rs) {
        var series = rs;
        if (!series) {
            return;
        }
        this.selectedSeries = (0, array_1.arrayRemove)(this.selectedSeries, series);
        series.selected.unsubscribe(this.updateSeriesSelected);
        this.hoveredSeries = (0, array_1.arrayRemove)(this.hoveredSeries, series);
        series.hovered.unsubscribe(this.updateSeriesHovered);
    };
    /** @inheritDoc */
    SeriesSelectionModifier3D.prototype.modifierMouseDown = function (args) {
        _super.prototype.modifierMouseDown.call(this, args);
        if (!this.checkExecuteConditions(args).isPrimary)
            return;
        if (!this.isAttached) {
            throw new Error("Should not call SeriesSelectionModifier3D.modifierMouseDown if not attached");
        }
    };
    /** @inheritDoc */
    SeriesSelectionModifier3D.prototype.modifierMouseMove = function (args) {
        var _a;
        _super.prototype.modifierMouseMove.call(this, args);
        if (!this.enableHover) {
            return;
        }
        this.updateHoverState((_a = this.mousePoint) !== null && _a !== void 0 ? _a : args.mousePoint);
    };
    /** @inheritDoc */
    SeriesSelectionModifier3D.prototype.onParentSurfaceRendered = function () {
        _super.prototype.onParentSurfaceRendered.call(this);
        if (!this.enableHover || !this.mousePoint) {
            return;
        }
        this.updateHoverState(this.mousePoint);
    };
    /** @inheritDoc */
    SeriesSelectionModifier3D.prototype.modifierMouseLeave = function (args) {
        var _a;
        _super.prototype.modifierMouseLeave.call(this, args);
        this.mousePoint = undefined;
        var allSeries = this.getIncludedRenderableSeries();
        var prevHovered = __spreadArray([], this.hoveredSeries, true);
        var nearestHitTestInfo = undefined;
        if (prevHovered.length > 0) {
            this.preventReentrancy = true;
            try {
                this.hoveredSeries = [];
                allSeries.forEach(function (s) { return (s.isHovered = false); });
                (_a = this.hoverChanged) === null || _a === void 0 ? void 0 : _a.raiseEvent(new HoveredChangedArgs3D_1.HoveredChangedArgs3D(this, this.hoveredSeries, allSeries, nearestHitTestInfo));
            }
            finally {
                this.preventReentrancy = false;
            }
        }
    };
    /** @inheritDoc */
    SeriesSelectionModifier3D.prototype.modifierPointerCancel = function (args) {
        this.modifierMouseLeave(args);
    };
    /** @inheritDoc */
    SeriesSelectionModifier3D.prototype.modifierMouseUp = function (args) {
        var _a;
        _super.prototype.modifierMouseUp.call(this, args);
        if (!this.checkExecuteConditions(args).isPrimary)
            return;
        if (!this.enableSelection) {
            return;
        }
        if (!this.isAttached) {
            throw new Error("Should not call SeriesSelectionModifier3D.modifierMouseUp if not attached");
        }
        var allSeries = this.getIncludedRenderableSeries();
        this.preventReentrancy = true;
        try {
            this.selectedSeries = [];
            allSeries.forEach(function (rs) { return (rs.isSelected = false); });
            var nearestHitTestInfo = this.findFirstHit(allSeries, args.mousePoint, this.hitTestRadius);
            if (nearestHitTestInfo === null || nearestHitTestInfo === void 0 ? void 0 : nearestHitTestInfo.isHit) {
                nearestHitTestInfo.renderableSeries.isSelected = true;
                this.selectedSeries.push(nearestHitTestInfo.renderableSeries);
            }
            (_a = this.selectionChanged) === null || _a === void 0 ? void 0 : _a.raiseEvent(new SelectionChangedArgs3D_1.SelectionChangedArgs3D(this, this.selectedSeries, allSeries, nearestHitTestInfo));
        }
        finally {
            this.preventReentrancy = false;
        }
    };
    SeriesSelectionModifier3D.prototype.toJSON = function () {
        var json = _super.prototype.toJSON.call(this);
        var options = {
            enableHover: this.enableHover,
            enableSelection: this.enableSelection,
            hitTestRadius: this.hitTestRadius,
            onHoverChanged: this.typeMap.get("onHoverChanged"),
            onSelectionChanged: this.typeMap.get("onSelectionChanged"),
            prioritizeClosestToCamera: this.prioritizeClosestToCamera
        };
        Object.assign(json.options, options);
        return json;
    };
    SeriesSelectionModifier3D.prototype.onRenderableSeriesCollectionChanged = function (args) {
        var _this = this;
        var _a, _b;
        (_a = args.getOldItems()) === null || _a === void 0 ? void 0 : _a.forEach(function (rs) { return _this.onDetachSeries(rs); });
        (_b = args.getNewItems()) === null || _b === void 0 ? void 0 : _b.filter(function (rs) { return _this.testIsIncludedSeries(rs); }).forEach(function (rs) { return _this.onAttachSeries(rs); });
    };
    SeriesSelectionModifier3D.prototype.updateHoverState = function (mousePoint) {
        var _this = this;
        var _a;
        var allSeries = this.getIncludedRenderableSeries();
        this.preventReentrancy = true;
        try {
            var prevHovered = __spreadArray([], this.hoveredSeries, true);
            this.hoveredSeries = [];
            var nearestHitTestInfo = this.findFirstHit(allSeries, mousePoint, this.hitTestRadius);
            var hoveredSeries_1 = (nearestHitTestInfo === null || nearestHitTestInfo === void 0 ? void 0 : nearestHitTestInfo.isHit) ? nearestHitTestInfo.renderableSeries : undefined;
            allSeries.forEach(function (s) { return (s.isHovered = s === hoveredSeries_1); });
            if (hoveredSeries_1) {
                this.hoveredSeries.push(hoveredSeries_1);
            }
            if (prevHovered.length !== this.hoveredSeries.length ||
                prevHovered.some(function (s) { return !_this.hoveredSeries.includes(s); })) {
                (_a = this.hoverChanged) === null || _a === void 0 ? void 0 : _a.raiseEvent(new HoveredChangedArgs3D_1.HoveredChangedArgs3D(this, this.hoveredSeries, allSeries, nearestHitTestInfo));
            }
        }
        finally {
            this.preventReentrancy = false;
        }
    };
    /**
     * Performs point-based hit-testing with a small pixel tolerance.
     * This improves hover detection for thin 3D lines without affecting click selection behavior.
     */
    SeriesSelectionModifier3D.prototype.findFirstHit = function (allSeries, mousePoint, searchRadius) {
        if (this.prioritizeClosestToCamera) {
            return this.findClosestHitToCamera(allSeries, mousePoint, searchRadius);
        }
        var centerHit = this.hitTestAtPoint(allSeries, mousePoint);
        if ((centerHit === null || centerHit === void 0 ? void 0 : centerHit.isHit) || searchRadius <= 0) {
            return centerHit;
        }
        for (var radius = 1; radius <= searchRadius; radius++) {
            var testPoints = [
                new Point_1.Point(mousePoint.x + radius, mousePoint.y),
                new Point_1.Point(mousePoint.x - radius, mousePoint.y),
                new Point_1.Point(mousePoint.x, mousePoint.y + radius),
                new Point_1.Point(mousePoint.x, mousePoint.y - radius),
                new Point_1.Point(mousePoint.x + radius, mousePoint.y + radius),
                new Point_1.Point(mousePoint.x + radius, mousePoint.y - radius),
                new Point_1.Point(mousePoint.x - radius, mousePoint.y + radius),
                new Point_1.Point(mousePoint.x - radius, mousePoint.y - radius)
            ];
            for (var _i = 0, testPoints_1 = testPoints; _i < testPoints_1.length; _i++) {
                var point = testPoints_1[_i];
                var hit = this.hitTestAtPoint(allSeries, point);
                if (hit === null || hit === void 0 ? void 0 : hit.isHit) {
                    return hit;
                }
            }
        }
        return undefined;
    };
    /**
     * Samples multiple screen positions around the mouse point up to searchRadius and
     * returns the hit whose world coordinate is closest to camera.
     */
    SeriesSelectionModifier3D.prototype.findClosestHitToCamera = function (allSeries, mousePoint, searchRadius) {
        var _this = this;
        var nearestHit;
        var nearestDistance = Number.POSITIVE_INFINITY;
        var updateNearest = function (hit) {
            if (!(hit === null || hit === void 0 ? void 0 : hit.isHit)) {
                return;
            }
            var distance = _this.distanceSquaredToCamera(hit);
            if (distance < nearestDistance) {
                nearestHit = hit;
                nearestDistance = distance;
            }
        };
        // test center first
        this.hitTestAtPointAllSeries(allSeries, mousePoint).forEach(updateNearest);
        if (searchRadius <= 0) {
            return nearestHit;
        }
        // Sample 16 radial directions
        var directions = [
            [1, 0],
            [-1, 0],
            [0, 1],
            [0, -1],
            [1, 1],
            [1, -1],
            [-1, 1],
            [-1, -1],
            [2, 1],
            [2, -1],
            [-2, 1],
            [-2, -1],
            [1, 2],
            [1, -2],
            [-1, 2],
            [-1, -2]
        ];
        for (var radius = 1; radius <= searchRadius; radius++) {
            for (var _i = 0, directions_1 = directions; _i < directions_1.length; _i++) {
                var _a = directions_1[_i], dx = _a[0], dy = _a[1];
                var length_1 = Math.sqrt(dx * dx + dy * dy);
                var point = new Point_1.Point(mousePoint.x + Math.round((dx / length_1) * radius), mousePoint.y + Math.round((dy / length_1) * radius));
                this.hitTestAtPointAllSeries(allSeries, point).forEach(updateNearest);
            }
        }
        return nearestHit;
    };
    SeriesSelectionModifier3D.prototype.hitTestAtPoint = function (allSeries, point) {
        return this.hitTestAtPointAllSeries(allSeries, point)[0];
    };
    SeriesSelectionModifier3D.prototype.hitTestAtPointAllSeries = function (allSeries, point) {
        return allSeries.map(function (rs) { return rs.hitTest(point); }).filter(function (ht) { return ht === null || ht === void 0 ? void 0 : ht.isHit; });
    };
    SeriesSelectionModifier3D.prototype.distanceSquaredToCamera = function (hitInfo) {
        var _a, _b;
        var cameraPosition = (_b = (_a = this.parentSurface) === null || _a === void 0 ? void 0 : _a.camera) === null || _b === void 0 ? void 0 : _b.position;
        var hitWorldCoords = hitInfo === null || hitInfo === void 0 ? void 0 : hitInfo.hitWorldCoords;
        if (!cameraPosition || !hitWorldCoords) {
            return Number.POSITIVE_INFINITY;
        }
        var dx = hitWorldCoords.x - cameraPosition.x;
        var dy = hitWorldCoords.y - cameraPosition.y;
        var dz = hitWorldCoords.z - cameraPosition.z;
        return dx * dx + dy * dy + dz * dz;
    };
    SeriesSelectionModifier3D.prototype.getHitTestRadius = function (hitTestRadius) {
        if (hitTestRadius === undefined || !Number.isFinite(hitTestRadius)) {
            return 2;
        }
        return Math.max(0, Math.floor(hitTestRadius));
    };
    /**
     * This function called when the user sets series.isSelected = true elsewhere in code and we want to sync the modifier
     */
    SeriesSelectionModifier3D.prototype.updateSeriesSelected = function (arg) {
        var _a;
        if (this.preventReentrancy) {
            return;
        }
        if (arg.isSelected) {
            if (!this.selectedSeries.includes(arg.sourceSeries)) {
                this.selectedSeries.push(arg.sourceSeries);
            }
        }
        else {
            this.selectedSeries = (0, array_1.arrayRemove)(this.selectedSeries, arg.sourceSeries);
        }
        (_a = this.selectionChanged) === null || _a === void 0 ? void 0 : _a.raiseEvent(new SelectionChangedArgs3D_1.SelectionChangedArgs3D(this, this.selectedSeries, this.getIncludedRenderableSeries(), undefined));
    };
    SeriesSelectionModifier3D.prototype.updateSeriesHovered = function (arg) {
        var _a;
        if (this.preventReentrancy) {
            return;
        }
        if (arg.hovered) {
            if (!this.hoveredSeries.includes(arg.sourceSeries)) {
                this.hoveredSeries.push(arg.sourceSeries);
            }
        }
        else {
            this.hoveredSeries = (0, array_1.arrayRemove)(this.hoveredSeries, arg.sourceSeries);
        }
        (_a = this.hoverChanged) === null || _a === void 0 ? void 0 : _a.raiseEvent(new HoveredChangedArgs3D_1.HoveredChangedArgs3D(this, this.hoveredSeries, this.getIncludedRenderableSeries(), undefined));
    };
    return SeriesSelectionModifier3D;
}(ChartModifierBase3D_1.ChartModifierBase3D));
exports.SeriesSelectionModifier3D = SeriesSelectionModifier3D;
