import vtkMatrixBuilder from '@kitware/vtk.js/Common/Core/MatrixBuilder';
import vtkMath from '@kitware/vtk.js/Common/Core/Math';
import vtkPlane from '@kitware/vtk.js/Common/DataModel/Plane';
import { vec2, vec3 } from 'gl-matrix';
import Events from '../enums/Events';
import ViewportStatus from '../enums/ViewportStatus';
import ViewportType from '../enums/ViewportType';
import renderingEngineCache from './renderingEngineCache';
import { actorIsA, isImageActor } from '../utilities/actorCheck';
import triggerEvent from '../utilities/triggerEvent';
import * as planar from '../utilities/planar';
import isEqual from '../utilities/isEqual';
import hasNaNValues from '../utilities/hasNaNValues';
import { RENDERING_DEFAULTS } from '../constants';
import { InterpolationType } from '../enums';
import { deepClone } from '../utilities/deepClone';
class Viewport {
    static { this.CameraViewPresentation = {
        rotation: true,
        pan: true,
        zoom: true,
        displayArea: true,
    }; }
    static { this.TransferViewPresentation = {
        windowLevel: true,
        paletteLut: true,
    }; }
    constructor(props) {
        this.insetImageMultiplier = 1.1;
        this.flipHorizontal = false;
        this.flipVertical = false;
        this.viewportStatus = ViewportStatus.NO_DATA;
        this._suppressCameraModifiedEvents = false;
        this.hasPixelSpacing = true;
        this.getProperties = () => ({});
        this.setRotation = (_rotation) => {
        };
        this.viewportWidgets = new Map();
        this.addWidget = (widgetId, widget) => {
            this.viewportWidgets.set(widgetId, widget);
        };
        this.getWidget = (id) => {
            return this.viewportWidgets.get(id);
        };
        this.getWidgets = () => {
            return Array.from(this.viewportWidgets.values());
        };
        this.removeWidgets = () => {
            const widgets = this.getWidgets();
            widgets.forEach((widget) => {
                if (widget.getEnabled()) {
                    widget.setEnabled(false);
                }
                if (widget.getActor && widget.getRenderer) {
                    const actor = widget.getActor();
                    const renderer = widget.getRenderer();
                    if (renderer && actor) {
                        renderer.removeActor(actor);
                    }
                }
            });
        };
        this.id = props.id;
        this.renderingEngineId = props.renderingEngineId;
        this.type = props.type;
        this.element = props.element;
        this.canvas = props.canvas;
        this.sx = props.sx;
        this.sy = props.sy;
        this.sWidth = props.sWidth;
        this.sHeight = props.sHeight;
        this._actors = new Map();
        this.element.setAttribute('data-viewport-uid', this.id);
        this.element.setAttribute('data-rendering-engine-uid', this.renderingEngineId);
        this.defaultOptions = deepClone(props.defaultOptions);
        this.suppressEvents = props.defaultOptions.suppressEvents
            ? props.defaultOptions.suppressEvents
            : false;
        this.options = deepClone(props.defaultOptions);
        this.isDisabled = false;
    }
    static get useCustomRenderingPipeline() {
        return false;
    }
    setRendered() {
        if (this.viewportStatus === ViewportStatus.NO_DATA ||
            this.viewportStatus === ViewportStatus.LOADING) {
            return;
        }
        this.viewportStatus = ViewportStatus.RENDERED;
    }
    getRenderingEngine() {
        return renderingEngineCache.get(this.renderingEngineId);
    }
    getRenderer() {
        const renderingEngine = this.getRenderingEngine();
        if (!renderingEngine || renderingEngine.hasBeenDestroyed) {
            throw new Error('Rendering engine has been destroyed');
        }
        return renderingEngine.offscreenMultiRenderWindow?.getRenderer(this.id);
    }
    render() {
        const renderingEngine = this.getRenderingEngine();
        renderingEngine.renderViewport(this.id);
    }
    setOptions(options, immediate = false) {
        this.options = structuredClone(options);
        if (this.options?.displayArea) {
            this.setDisplayArea(this.options?.displayArea);
        }
        if (immediate) {
            this.render();
        }
    }
    reset(immediate = false) {
        this.options = structuredClone(this.defaultOptions);
        if (immediate) {
            this.render();
        }
    }
    getSliceViewInfo() {
        throw new Error('Method not implemented.');
    }
    flip({ flipHorizontal, flipVertical }) {
        const imageData = this.getDefaultImageData();
        if (!imageData) {
            return;
        }
        const camera = this.getCamera();
        const { viewPlaneNormal, viewUp, focalPoint, position } = camera;
        const viewRight = vec3.cross(vec3.create(), viewPlaneNormal, viewUp);
        let viewUpToSet = vec3.copy(vec3.create(), viewUp);
        const viewPlaneNormalToSet = vec3.negate(vec3.create(), viewPlaneNormal);
        const distance = vec3.distance(position, focalPoint);
        const dimensions = imageData.getDimensions();
        const middleIJK = dimensions.map((d) => Math.floor(d / 2));
        const idx = [middleIJK[0], middleIJK[1], middleIJK[2]];
        const centeredFocalPoint = imageData.indexToWorld(idx, vec3.create());
        const resetFocalPoint = this._getFocalPointForResetCamera(centeredFocalPoint, camera, { resetPan: true, resetToCenter: false });
        const panDir = vec3.subtract(vec3.create(), focalPoint, resetFocalPoint);
        const panValue = vec3.length(panDir);
        const getPanDir = (mirrorVec) => {
            const panDirMirror = vec3.scale(vec3.create(), mirrorVec, 2 * vec3.dot(panDir, mirrorVec));
            vec3.subtract(panDirMirror, panDirMirror, panDir);
            vec3.normalize(panDirMirror, panDirMirror);
            return panDirMirror;
        };
        if (flipHorizontal) {
            const panDirMirror = getPanDir(viewUpToSet);
            const newFocalPoint = vec3.scaleAndAdd(vec3.create(), resetFocalPoint, panDirMirror, panValue);
            const newPosition = vec3.scaleAndAdd(vec3.create(), newFocalPoint, viewPlaneNormalToSet, distance);
            this.setCamera({
                viewPlaneNormal: viewPlaneNormalToSet,
                position: newPosition,
                focalPoint: newFocalPoint,
            });
            this.flipHorizontal = !this.flipHorizontal;
        }
        if (flipVertical) {
            viewUpToSet = vec3.negate(viewUpToSet, viewUp);
            const panDirMirror = getPanDir(viewRight);
            const newFocalPoint = vec3.scaleAndAdd(vec3.create(), resetFocalPoint, panDirMirror, panValue);
            const newPosition = vec3.scaleAndAdd(vec3.create(), newFocalPoint, viewPlaneNormalToSet, distance);
            this.setCamera({
                focalPoint: newFocalPoint,
                viewPlaneNormal: viewPlaneNormalToSet,
                viewUp: viewUpToSet,
                position: newPosition,
            });
            this.flipVertical = !this.flipVertical;
        }
        this.render();
    }
    getDefaultImageData() {
        const actorEntry = this.getDefaultActor();
        if (actorEntry && isImageActor(actorEntry)) {
            return actorEntry.actor.getMapper().getInputData();
        }
    }
    getDefaultActor() {
        return this.getActors()[0];
    }
    getActors() {
        return Array.from(this._actors.values());
    }
    getActorUIDs() {
        return Array.from(this._actors.keys());
    }
    getActor(actorUID) {
        return this._actors.get(actorUID);
    }
    getImageActor(volumeId) {
        const actorEntries = this.getActors();
        let actorEntry = actorEntries[0];
        if (volumeId) {
            actorEntry = actorEntries.find((a) => a.referencedId === volumeId);
        }
        if (!actorEntry || !isImageActor(actorEntry)) {
            return null;
        }
        const actor = actorEntry.actor;
        return actor;
    }
    getActorUIDByIndex(index) {
        const actor = this.getActors()[index];
        if (actor) {
            return actor.uid;
        }
    }
    getActorByIndex(index) {
        return this.getActors()[index];
    }
    setActors(actors) {
        this.removeAllActors();
        this.addActors(actors, { resetCamera: true });
    }
    _removeActor(actorUID) {
        const actorEntry = this.getActor(actorUID);
        if (!actorEntry) {
            console.warn(`Actor ${actorUID} does not exist for this viewport`);
            return;
        }
        const renderer = this.getRenderer();
        renderer.removeViewProp(actorEntry.actor);
        this._actors.delete(actorUID);
    }
    removeActors(actorUIDs) {
        actorUIDs.forEach((actorUID) => {
            this._removeActor(actorUID);
        });
    }
    addActors(actors, options = {}) {
        const { resetCamera = false } = options;
        const renderingEngine = this.getRenderingEngine();
        if (!renderingEngine || renderingEngine.hasBeenDestroyed) {
            console.warn('Viewport::addActors::Rendering engine has not been initialized or has been destroyed');
            return;
        }
        actors.forEach((actor) => {
            this.addActor(actor);
        });
        const prevViewPresentation = this.getViewPresentation();
        const prevViewRef = this.getViewReference();
        this.resetCamera();
        if (!resetCamera) {
            this.setViewReference(prevViewRef);
            this.setViewPresentation(prevViewPresentation);
        }
    }
    addActor(actorEntry) {
        const { uid: actorUID, actor } = actorEntry;
        const renderingEngine = this.getRenderingEngine();
        if (!renderingEngine || renderingEngine.hasBeenDestroyed) {
            console.warn(`Cannot add actor UID of ${actorUID} Rendering Engine has been destroyed`);
            return;
        }
        if (!actorUID || !actor) {
            throw new Error('Actors should have uid and vtk Actor properties');
        }
        if (this.getActor(actorUID)) {
            console.warn(`Actor ${actorUID} already exists for this viewport`);
            return;
        }
        const renderer = this.getRenderer();
        renderer?.addActor(actor);
        this._actors.set(actorUID, Object.assign({}, actorEntry));
        this.updateCameraClippingPlanesAndRange();
    }
    removeAllActors() {
        this.getRenderer()?.removeAllViewProps();
        this._actors = new Map();
        return;
    }
    resetCameraNoEvent() {
        this._suppressCameraModifiedEvents = true;
        this.resetCamera();
        this._suppressCameraModifiedEvents = false;
    }
    setCameraNoEvent(camera) {
        this._suppressCameraModifiedEvents = true;
        this.setCamera(camera);
        this._suppressCameraModifiedEvents = false;
    }
    _getViewImageDataIntersections(imageData, focalPoint, normal) {
        const A = normal[0];
        const B = normal[1];
        const C = normal[2];
        const D = A * focalPoint[0] + B * focalPoint[1] + C * focalPoint[2];
        const bounds = imageData.getBounds();
        const edges = this._getEdges(bounds);
        const intersections = [];
        for (const edge of edges) {
            const [[x0, y0, z0], [x1, y1, z1]] = edge;
            if (A * (x1 - x0) + B * (y1 - y0) + C * (z1 - z0) === 0) {
                continue;
            }
            const intersectionPoint = planar.linePlaneIntersection([x0, y0, z0], [x1, y1, z1], [A, B, C, D]);
            if (this._isInBounds(intersectionPoint, bounds)) {
                intersections.push(intersectionPoint);
            }
        }
        return intersections;
    }
    setInterpolationType(_interpolationType, _arg) {
    }
    setDisplayArea(displayArea, suppressEvents = false) {
        if (!displayArea) {
            return;
        }
        const { storeAsInitialCamera, type: areaType } = displayArea;
        if (storeAsInitialCamera) {
            this.options.displayArea = displayArea;
        }
        const { _suppressCameraModifiedEvents } = this;
        this._suppressCameraModifiedEvents = true;
        this.setCamera(this.fitToCanvasCamera);
        if (areaType === 'SCALE') {
            this.setDisplayAreaScale(displayArea);
        }
        else {
            this.setInterpolationType(this.getProperties()?.interpolationType ?? InterpolationType.LINEAR);
            this.setDisplayAreaFit(displayArea);
        }
        if (storeAsInitialCamera) {
            this.initialCamera = this.getCamera();
        }
        this._suppressCameraModifiedEvents = _suppressCameraModifiedEvents;
        if (!suppressEvents && !_suppressCameraModifiedEvents) {
            const eventDetail = {
                viewportId: this.id,
                displayArea: displayArea,
                storeAsInitialCamera: storeAsInitialCamera,
            };
            triggerEvent(this.element, Events.DISPLAY_AREA_MODIFIED, eventDetail);
            this.setCamera(this.getCamera());
        }
    }
    setDisplayAreaScale(displayArea) {
        const { scale = 1 } = displayArea;
        const canvas = this.canvas;
        const height = canvas.height;
        const width = canvas.width;
        if (height < 8 || width < 8) {
            return;
        }
        const imageData = this.getDefaultImageData();
        const spacingWorld = imageData.getSpacing();
        const spacing = spacingWorld[1];
        this.setInterpolationType(InterpolationType.NEAREST);
        this.setCamera({ parallelScale: (height * spacing) / (2 * scale) });
        delete displayArea.imageArea;
        this.setDisplayAreaFit(displayArea);
        const { focalPoint, position, viewUp, viewPlaneNormal } = this.getCamera();
        const focalChange = vec3.create();
        if (canvas.height % 2) {
            vec3.scaleAndAdd(focalChange, focalChange, viewUp, scale * 0.5 * spacing);
        }
        if (canvas.width % 2) {
            const viewRight = vec3.cross(vec3.create(), viewUp, viewPlaneNormal);
            vec3.scaleAndAdd(focalChange, focalChange, viewRight, scale * 0.5 * spacing);
        }
        if (!focalChange[0] && !focalChange[1] && !focalChange[2]) {
            return;
        }
        this.setCamera({
            focalPoint: vec3.add(vec3.create(), focalPoint, focalChange),
            position: vec3.add(vec3.create(), position, focalChange),
        });
    }
    setDisplayAreaFit(displayArea) {
        const { imageArea, imageCanvasPoint } = displayArea;
        const devicePixelRatio = window?.devicePixelRatio || 1;
        const imageData = this.getDefaultImageData();
        if (!imageData) {
            return;
        }
        const canvasWidth = this.sWidth / devicePixelRatio;
        const canvasHeight = this.sHeight / devicePixelRatio;
        const dimensions = imageData.getDimensions();
        const canvasZero = this.worldToCanvas(imageData.indexToWorld([0, 0, 0]));
        const canvasEdge = this.worldToCanvas(imageData.indexToWorld([
            dimensions[0],
            dimensions[1],
            dimensions[2],
        ]));
        const canvasImage = [
            Math.abs(canvasEdge[0] - canvasZero[0]),
            Math.abs(canvasEdge[1] - canvasZero[1]),
        ];
        const [imgWidth, imgHeight] = canvasImage;
        let zoom = this.getZoom() / this.insetImageMultiplier;
        if (imageArea) {
            const [areaX, areaY] = imageArea;
            const currentScale = Math.max(Math.abs(imgWidth / canvasWidth), Math.abs(imgHeight / canvasHeight));
            const requireX = Math.abs((areaX * imgWidth) / canvasWidth);
            const requireY = Math.abs((areaY * imgHeight) / canvasHeight);
            const initZoom = this.getZoom();
            const fitZoom = this.getZoom(this.fitToCanvasCamera);
            const absZoom = requireX > requireY ? currentScale / requireX : currentScale / requireY;
            const applyZoom = (absZoom * initZoom) / fitZoom;
            zoom = applyZoom;
            this.setZoom(this.insetImageMultiplier * zoom, false);
        }
        if (imageCanvasPoint) {
            console.log('Starting pan update zoom=', zoom);
            const { imagePoint, canvasPoint = imagePoint || [0.5, 0.5] } = imageCanvasPoint;
            const [canvasX, canvasY] = canvasPoint;
            const canvasPanX = canvasWidth * (canvasX - 0.5);
            const canvasPanY = canvasHeight * (canvasY - 0.5);
            const [imageX, imageY] = imagePoint || canvasPoint;
            const useZoom = zoom;
            const imagePanX = this.insetImageMultiplier * useZoom * imgWidth * (0.5 - imageX);
            const imagePanY = this.insetImageMultiplier * useZoom * imgHeight * (0.5 - imageY);
            const newPositionX = imagePanX + canvasPanX;
            const newPositionY = imagePanY + canvasPanY;
            const deltaPoint2 = [newPositionX, newPositionY];
            console.log('delta point', newPositionX, this.getPan()[0], imagePanX, canvasPanX);
            vec2.add(deltaPoint2, deltaPoint2, this.getPan());
            this.setPan(deltaPoint2, false);
        }
    }
    getDisplayArea() {
        return this.options?.displayArea;
    }
    resetCamera(options) {
        const { resetPan = true, resetZoom = true, resetToCenter = true, storeAsInitialCamera = true, } = options || {};
        const renderer = this.getRenderer();
        this.setCameraNoEvent({
            flipHorizontal: false,
            flipVertical: false,
        });
        const previousCamera = this.getCamera();
        const bounds = renderer.computeVisiblePropBounds();
        const focalPoint = [0, 0, 0];
        const imageData = this.getDefaultImageData();
        if (imageData) {
            const spc = imageData.getSpacing();
            bounds[0] = bounds[0] + spc[0] / 2;
            bounds[1] = bounds[1] - spc[0] / 2;
            bounds[2] = bounds[2] + spc[1] / 2;
            bounds[3] = bounds[3] - spc[1] / 2;
            bounds[4] = bounds[4] + spc[2] / 2;
            bounds[5] = bounds[5] - spc[2] / 2;
        }
        const activeCamera = this.getVtkActiveCamera();
        const viewPlaneNormal = activeCamera.getViewPlaneNormal();
        const viewUp = activeCamera.getViewUp();
        focalPoint[0] = (bounds[0] + bounds[1]) / 2.0;
        focalPoint[1] = (bounds[2] + bounds[3]) / 2.0;
        focalPoint[2] = (bounds[4] + bounds[5]) / 2.0;
        if (imageData) {
            const dimensions = imageData.getDimensions();
            const middleIJK = dimensions.map((d) => Math.floor(d / 2));
            const idx = [middleIJK[0], middleIJK[1], middleIJK[2]];
            imageData.indexToWorld(idx, focalPoint);
        }
        const { widthWorld, heightWorld } = this._getWorldDistanceViewUpAndViewRight(bounds, viewUp, viewPlaneNormal);
        const canvasSize = [this.sWidth, this.sHeight];
        const boundsAspectRatio = widthWorld / heightWorld;
        const canvasAspectRatio = canvasSize[0] / canvasSize[1];
        const scaleFactor = boundsAspectRatio / canvasAspectRatio;
        const parallelScale = scaleFactor < 1
            ? (this.insetImageMultiplier * heightWorld) / 2
            : (this.insetImageMultiplier * heightWorld * scaleFactor) / 2;
        const radius = Viewport.boundsRadius(bounds) *
            (this.type === ViewportType.VOLUME_3D ? 10 : 1);
        const distance = this.insetImageMultiplier * radius;
        const viewUpToSet = Math.abs(vtkMath.dot(viewUp, viewPlaneNormal)) > 0.999
            ? [-viewUp[2], viewUp[0], viewUp[1]]
            : viewUp;
        const focalPointToSet = this._getFocalPointForResetCamera(focalPoint, previousCamera, { resetPan, resetToCenter });
        const positionToSet = [
            focalPointToSet[0] + distance * viewPlaneNormal[0],
            focalPointToSet[1] + distance * viewPlaneNormal[1],
            focalPointToSet[2] + distance * viewPlaneNormal[2],
        ];
        renderer.resetCameraClippingRange(bounds);
        const clippingRangeToUse = [
            -RENDERING_DEFAULTS.MAXIMUM_RAY_DISTANCE,
            RENDERING_DEFAULTS.MAXIMUM_RAY_DISTANCE,
        ];
        activeCamera.setPhysicalScale(radius);
        activeCamera.setPhysicalTranslation(-focalPointToSet[0], -focalPointToSet[1], -focalPointToSet[2]);
        this.setCamera({
            parallelScale: resetZoom ? parallelScale : previousCamera.parallelScale,
            focalPoint: focalPointToSet,
            position: positionToSet,
            viewAngle: 90,
            viewUp: viewUpToSet,
            clippingRange: clippingRangeToUse,
        });
        const modifiedCamera = this.getCamera();
        this.setFitToCanvasCamera(this.getCamera());
        if (storeAsInitialCamera) {
            this.setInitialCamera(modifiedCamera);
        }
        if (resetZoom) {
            this.setZoom(1, storeAsInitialCamera);
        }
        const RESET_CAMERA_EVENT = {
            type: 'ResetCameraEvent',
            renderer,
        };
        renderer.invokeEvent(RESET_CAMERA_EVENT);
        this.triggerCameraModifiedEventIfNecessary(previousCamera, modifiedCamera);
        if (imageData &&
            this.options.displayArea &&
            resetZoom &&
            resetPan &&
            resetToCenter) {
            this.setDisplayArea(this.options.displayArea);
        }
        return true;
    }
    setInitialCamera(camera) {
        this.initialCamera = camera;
    }
    setFitToCanvasCamera(camera) {
        this.fitToCanvasCamera = camera;
    }
    getPan(initialCamera = this.initialCamera) {
        if (!initialCamera) {
            return [0, 0];
        }
        const activeCamera = this.getVtkActiveCamera();
        const focalPoint = activeCamera.getFocalPoint();
        const zero3 = this.canvasToWorld([0, 0]);
        const initialCanvasFocal = this.worldToCanvas(vec3.subtract([0, 0, 0], initialCamera.focalPoint, zero3));
        const currentCanvasFocal = this.worldToCanvas(vec3.subtract([0, 0, 0], focalPoint, zero3));
        const result = vec2.subtract([0, 0], initialCanvasFocal, currentCanvasFocal);
        return result;
    }
    getCurrentImageIdIndex() {
        throw new Error('Not implemented');
    }
    getSliceIndex() {
        throw new Error('Not implemented');
    }
    getImageData() {
        throw new Error('Not implemented');
    }
    getViewReferenceId(_specifier) {
        return null;
    }
    setPan(pan, storeAsInitialCamera = false) {
        const previousCamera = this.getCamera();
        const { focalPoint, position } = previousCamera;
        const zero3 = this.canvasToWorld([0, 0]);
        const delta2 = vec2.subtract([0, 0], pan, this.getPan());
        if (Math.abs(delta2[0]) < 1 &&
            Math.abs(delta2[1]) < 1 &&
            !storeAsInitialCamera) {
            return;
        }
        const delta = vec3.subtract(vec3.create(), this.canvasToWorld(delta2), zero3);
        const newFocal = vec3.subtract(vec3.create(), focalPoint, delta);
        const newPosition = vec3.subtract(vec3.create(), position, delta);
        this.setCamera({
            ...previousCamera,
            focalPoint: newFocal,
            position: newPosition,
        }, storeAsInitialCamera);
    }
    getZoom(compareCamera = this.initialCamera) {
        if (!compareCamera) {
            return 1;
        }
        const activeCamera = this.getVtkActiveCamera();
        const { parallelScale: initialParallelScale } = compareCamera;
        return initialParallelScale / activeCamera.getParallelScale();
    }
    setZoom(value, storeAsInitialCamera = false) {
        const camera = this.getCamera();
        const { parallelScale: initialParallelScale } = this.initialCamera;
        const parallelScale = initialParallelScale / value;
        if (camera.parallelScale === parallelScale && !storeAsInitialCamera) {
            return;
        }
        this.setCamera({
            ...camera,
            parallelScale,
        }, storeAsInitialCamera);
    }
    _getFocalPointForViewPlaneReset(imageData) {
        const { focalPoint, viewPlaneNormal: normal } = this.getCamera();
        const intersections = this._getViewImageDataIntersections(imageData, focalPoint, normal);
        let x = 0;
        let y = 0;
        let z = 0;
        intersections.forEach(([point_x, point_y, point_z]) => {
            x += point_x;
            y += point_y;
            z += point_z;
        });
        const newFocalPoint = [
            x / intersections.length,
            y / intersections.length,
            z / intersections.length,
        ];
        return newFocalPoint;
    }
    getCanvas() {
        return this.canvas;
    }
    getVtkActiveCamera() {
        const renderer = this.getRenderer();
        if (!renderer) {
            console.warn('No renderer found for the viewport');
            return null;
        }
        return renderer.getActiveCamera();
    }
    getCameraNoRotation() {
        const vtkCamera = this.getVtkActiveCamera();
        const sanitizeVector = (vector, defaultValue) => {
            return vector.some((v) => isNaN(v)) ? defaultValue : vector;
        };
        const viewUp = sanitizeVector([...vtkCamera.getViewUp()], [0, 1, 0]);
        const viewPlaneNormal = sanitizeVector([...vtkCamera.getViewPlaneNormal()], [0, 0, -1]);
        const position = sanitizeVector([...vtkCamera.getPosition()], [0, 0, 1]);
        const focalPoint = sanitizeVector([...vtkCamera.getFocalPoint()], [0, 0, 0]);
        return {
            viewUp,
            viewPlaneNormal,
            position,
            focalPoint,
            parallelProjection: vtkCamera.getParallelProjection(),
            parallelScale: vtkCamera.getParallelScale(),
            viewAngle: vtkCamera.getViewAngle(),
            flipHorizontal: this.flipHorizontal,
            flipVertical: this.flipVertical,
        };
    }
    getCamera() {
        const camera = this.getCameraNoRotation();
        return {
            ...camera,
            rotation: this.getRotation(),
        };
    }
    setCamera(cameraInterface, storeAsInitialCamera = false) {
        const vtkCamera = this.getVtkActiveCamera();
        const previousCamera = this.getCamera();
        const updatedCamera = Object.assign({}, previousCamera, cameraInterface);
        const { viewUp, viewPlaneNormal, position, focalPoint, parallelScale, viewAngle, flipHorizontal, flipVertical, clippingRange, } = cameraInterface;
        if (flipHorizontal !== undefined) {
            const flipH = (flipHorizontal && !this.flipHorizontal) ||
                (!flipHorizontal && this.flipHorizontal);
            if (flipH) {
                this.flip({ flipHorizontal: flipH });
            }
        }
        if (flipVertical !== undefined) {
            const flipV = (flipVertical && !this.flipVertical) ||
                (!flipVertical && this.flipVertical);
            if (flipV) {
                this.flip({ flipVertical: flipV });
            }
        }
        if (viewUp !== undefined) {
            vtkCamera.setViewUp(viewUp);
        }
        if (viewPlaneNormal !== undefined) {
            vtkCamera.setDirectionOfProjection(-viewPlaneNormal[0], -viewPlaneNormal[1], -viewPlaneNormal[2]);
        }
        if (position !== undefined) {
            vtkCamera.setPosition(...position);
        }
        if (focalPoint !== undefined) {
            vtkCamera.setFocalPoint(...focalPoint);
        }
        if (parallelScale !== undefined) {
            vtkCamera.setParallelScale(parallelScale);
        }
        if (viewAngle !== undefined) {
            vtkCamera.setViewAngle(viewAngle);
        }
        if (clippingRange !== undefined) {
            vtkCamera.setClippingRange(clippingRange);
        }
        const prevFocalPoint = previousCamera.focalPoint;
        const prevViewUp = previousCamera.viewUp;
        if ((prevFocalPoint && focalPoint) || (prevViewUp && viewUp)) {
            const currentViewPlaneNormal = vtkCamera.getViewPlaneNormal();
            const currentViewUp = vtkCamera.getViewUp();
            let cameraModifiedOutOfPlane = false;
            let viewUpHasChanged = false;
            if (focalPoint) {
                const deltaCamera = [
                    focalPoint[0] - prevFocalPoint[0],
                    focalPoint[1] - prevFocalPoint[1],
                    focalPoint[2] - prevFocalPoint[2],
                ];
                cameraModifiedOutOfPlane =
                    Math.abs(vtkMath.dot(deltaCamera, currentViewPlaneNormal)) > 0;
            }
            if (viewUp) {
                viewUpHasChanged = !isEqual(currentViewUp, prevViewUp);
            }
            if (cameraModifiedOutOfPlane || viewUpHasChanged) {
                const actorEntry = this.getDefaultActor();
                if (!actorEntry?.actor) {
                    return;
                }
                if (!actorIsA(actorEntry, 'vtkActor')) {
                    this.updateClippingPlanesForActors(updatedCamera);
                }
                if (actorIsA(actorEntry, 'vtkImageSlice') ||
                    this.type === ViewportType.VOLUME_3D) {
                    const renderer = this.getRenderer();
                    renderer.resetCameraClippingRange();
                }
            }
        }
        if (storeAsInitialCamera) {
            this.setInitialCamera(updatedCamera);
        }
        this.triggerCameraModifiedEventIfNecessary(previousCamera, this.getCamera());
    }
    triggerCameraModifiedEventIfNecessary(previousCamera, updatedCamera) {
        if (!this._suppressCameraModifiedEvents && !this.suppressEvents) {
            const eventDetail = {
                previousCamera,
                camera: updatedCamera,
                element: this.element,
                viewportId: this.id,
                renderingEngineId: this.renderingEngineId,
            };
            triggerEvent(this.element, Events.CAMERA_MODIFIED, eventDetail);
        }
    }
    updateCameraClippingPlanesAndRange() {
        const currentCamera = this.getCamera();
        this.updateClippingPlanesForActors(currentCamera);
        this.getRenderer().resetCameraClippingRange();
    }
    async updateClippingPlanesForActors(updatedCamera) {
        const actorEntries = this.getActors();
        actorEntries.map((actorEntry) => {
            if (!actorEntry.actor) {
                return;
            }
            const mapper = actorEntry.actor.getMapper();
            let vtkPlanes = actorEntry?.clippingFilter
                ? actorEntry?.clippingFilter.getClippingPlanes()
                : mapper.getClippingPlanes();
            if (vtkPlanes.length === 0 && actorEntry?.clippingFilter) {
                vtkPlanes = [vtkPlane.newInstance(), vtkPlane.newInstance()];
            }
            let slabThickness = RENDERING_DEFAULTS.MINIMUM_SLAB_THICKNESS;
            if (actorEntry.slabThickness) {
                slabThickness = actorEntry.slabThickness;
            }
            const { viewPlaneNormal, focalPoint } = updatedCamera;
            this.setOrientationOfClippingPlanes(vtkPlanes, slabThickness, viewPlaneNormal, focalPoint);
            triggerEvent(this.element, Events.CLIPPING_PLANES_UPDATED, {
                actorEntry,
                focalPoint,
                vtkPlanes,
                viewport: this,
            });
        });
    }
    setOrientationOfClippingPlanes(vtkPlanes, slabThickness, viewPlaneNormal, focalPoint) {
        if (vtkPlanes.length < 2) {
            return;
        }
        const scaledDistance = [
            viewPlaneNormal[0],
            viewPlaneNormal[1],
            viewPlaneNormal[2],
        ];
        vtkMath.multiplyScalar(scaledDistance, slabThickness);
        vtkPlanes[0].setNormal(viewPlaneNormal);
        const newOrigin1 = [0, 0, 0];
        vtkMath.subtract(focalPoint, scaledDistance, newOrigin1);
        vtkPlanes[0].setOrigin(newOrigin1);
        vtkPlanes[1].setNormal(-viewPlaneNormal[0], -viewPlaneNormal[1], -viewPlaneNormal[2]);
        const newOrigin2 = [0, 0, 0];
        vtkMath.add(focalPoint, scaledDistance, newOrigin2);
        vtkPlanes[1].setOrigin(newOrigin2);
    }
    getClippingPlanesForActor(actorEntry) {
        if (!actorEntry) {
            actorEntry = this.getDefaultActor();
        }
        if (!actorEntry.actor) {
            throw new Error('Invalid actor entry: Actor is undefined');
        }
        const mapper = actorEntry.actor.getMapper();
        let vtkPlanes = actorEntry?.clippingFilter
            ? actorEntry?.clippingFilter.getClippingPlanes()
            : mapper.getClippingPlanes();
        if (vtkPlanes.length === 0 && actorEntry?.clippingFilter) {
            vtkPlanes = [vtkPlane.newInstance(), vtkPlane.newInstance()];
        }
        return vtkPlanes;
    }
    _getWorldDistanceViewUpAndViewRight(bounds, viewUp, viewPlaneNormal) {
        const viewUpCorners = this._getCorners(bounds);
        const viewRightCorners = this._getCorners(bounds);
        const viewRight = vec3.cross(vec3.create(), viewUp, viewPlaneNormal);
        let transform = vtkMatrixBuilder
            .buildFromDegree()
            .identity()
            .rotateFromDirections(viewUp, [1, 0, 0]);
        viewUpCorners.forEach((pt) => transform.apply(pt));
        let minY = Infinity;
        let maxY = -Infinity;
        for (let i = 0; i < 8; i++) {
            const y = viewUpCorners[i][0];
            if (y > maxY) {
                maxY = y;
            }
            if (y < minY) {
                minY = y;
            }
        }
        transform = vtkMatrixBuilder
            .buildFromDegree()
            .identity()
            .rotateFromDirections([viewRight[0], viewRight[1], viewRight[2]], [1, 0, 0]);
        viewRightCorners.forEach((pt) => transform.apply(pt));
        let minX = Infinity;
        let maxX = -Infinity;
        for (let i = 0; i < 8; i++) {
            const x = viewRightCorners[i][0];
            if (x > maxX) {
                maxX = x;
            }
            if (x < minX) {
                minX = x;
            }
        }
        return { widthWorld: maxX - minX, heightWorld: maxY - minY };
    }
    getViewReference(viewRefSpecifier) {
        const { focalPoint: cameraFocalPoint, viewPlaneNormal, viewUp, } = this.getCamera();
        const target = {
            FrameOfReferenceUID: this.getFrameOfReferenceUID(),
            cameraFocalPoint,
            viewPlaneNormal,
            viewUp,
            sliceIndex: viewRefSpecifier?.sliceIndex ?? this.getSliceIndex(),
        };
        return target;
    }
    isReferenceViewable(viewRef, options) {
        if (viewRef.FrameOfReferenceUID &&
            viewRef.FrameOfReferenceUID !== this.getFrameOfReferenceUID()) {
            return false;
        }
        const { viewPlaneNormal } = viewRef;
        const camera = this.getCamera();
        if (viewPlaneNormal &&
            !isEqual(viewPlaneNormal, camera.viewPlaneNormal) &&
            !isEqual(vec3.negate(camera.viewPlaneNormal, camera.viewPlaneNormal), viewPlaneNormal)) {
            return options?.withOrientation;
        }
        return true;
    }
    getViewPresentation(viewPresSel = {
        rotation: true,
        displayArea: true,
        zoom: true,
        pan: true,
        flipHorizontal: true,
        flipVertical: true,
    }) {
        const target = {};
        const { rotation, displayArea, zoom, pan, flipHorizontal, flipVertical } = viewPresSel;
        if (rotation) {
            target.rotation = this.getRotation();
        }
        if (displayArea) {
            target.displayArea = this.getDisplayArea();
        }
        const initZoom = this.getZoom();
        if (zoom) {
            target.zoom = initZoom;
        }
        if (pan) {
            target.pan = this.getPan();
            vec2.scale(target.pan, target.pan, 1 / initZoom);
        }
        if (flipHorizontal) {
            target.flipHorizontal = this.flipHorizontal;
        }
        if (flipVertical) {
            target.flipVertical = this.flipVertical;
        }
        return target;
    }
    setViewReference(viewRef) {
    }
    setViewPresentation(viewPres) {
        if (!viewPres) {
            return;
        }
        const { displayArea, zoom = this.getZoom(), pan, rotation, flipHorizontal = this.flipHorizontal, flipVertical = this.flipVertical, } = viewPres;
        if (displayArea !== this.getDisplayArea()) {
            this.setDisplayArea(displayArea);
        }
        this.setZoom(zoom);
        if (pan) {
            this.setPan(vec2.scale([0, 0], pan, zoom));
        }
        if (rotation >= 0) {
            this.setRotation(rotation);
        }
        if (flipHorizontal || flipVertical) {
            this.flip({ flipHorizontal, flipVertical });
        }
    }
    _getCorners(bounds) {
        return [
            [bounds[0], bounds[2], bounds[4]],
            [bounds[0], bounds[2], bounds[5]],
            [bounds[0], bounds[3], bounds[4]],
            [bounds[0], bounds[3], bounds[5]],
            [bounds[1], bounds[2], bounds[4]],
            [bounds[1], bounds[2], bounds[5]],
            [bounds[1], bounds[3], bounds[4]],
            [bounds[1], bounds[3], bounds[5]],
        ];
    }
    _getFocalPointForResetCamera(centeredFocalPoint, previousCamera, { resetPan = true, resetToCenter = true }) {
        if (resetToCenter && resetPan) {
            return centeredFocalPoint;
        }
        if (resetToCenter && !resetPan) {
            return hasNaNValues(previousCamera.focalPoint)
                ? centeredFocalPoint
                : previousCamera.focalPoint;
        }
        if (!resetToCenter && resetPan) {
            const oldCamera = previousCamera;
            const oldFocalPoint = oldCamera.focalPoint;
            const oldViewPlaneNormal = oldCamera.viewPlaneNormal;
            const vectorFromOldFocalPointToCenteredFocalPoint = vec3.subtract(vec3.create(), centeredFocalPoint, oldFocalPoint);
            const distanceFromOldFocalPointToCenteredFocalPoint = vec3.dot(vectorFromOldFocalPointToCenteredFocalPoint, oldViewPlaneNormal);
            const newFocalPoint = vec3.scaleAndAdd(vec3.create(), centeredFocalPoint, oldViewPlaneNormal, -1 * distanceFromOldFocalPointToCenteredFocalPoint);
            return [newFocalPoint[0], newFocalPoint[1], newFocalPoint[2]];
        }
        if (!resetPan && !resetToCenter) {
            return hasNaNValues(previousCamera.focalPoint)
                ? centeredFocalPoint
                : previousCamera.focalPoint;
        }
    }
    _isInBounds(point, bounds) {
        const [xMin, xMax, yMin, yMax, zMin, zMax] = bounds;
        const [x, y, z] = point;
        if (x < xMin || x > xMax || y < yMin || y > yMax || z < zMin || z > zMax) {
            return false;
        }
        return true;
    }
    _getEdges(bounds) {
        const [p1, p2, p3, p4, p5, p6, p7, p8] = this._getCorners(bounds);
        return [
            [p1, p2],
            [p1, p5],
            [p1, p3],
            [p2, p4],
            [p2, p6],
            [p3, p4],
            [p3, p7],
            [p4, p8],
            [p5, p7],
            [p5, p6],
            [p6, p8],
            [p7, p8],
        ];
    }
    static boundsRadius(bounds) {
        const w1 = (bounds[1] - bounds[0]) ** 2;
        const w2 = (bounds[3] - bounds[2]) ** 2;
        const w3 = (bounds[5] - bounds[4]) ** 2;
        const radius = Math.sqrt(w1 + w2 + w3 || 1) * 0.5;
        return radius;
    }
    setDataIds(_imageIds, _options) {
        throw new Error('Unsupported operatoin setDataIds');
    }
}
export default Viewport;
