import { getEnabledElement, cache, utilities as csUtils, Enums, eventTarget, BaseVolumeViewport, } from '@cornerstonejs/core';
import { BaseTool } from '../base';
import SegmentationRepresentations from '../../enums/SegmentationRepresentations';
import { getActiveSegmentation } from '../../stateManagement/segmentation/getActiveSegmentation';
import { getLockedSegmentIndices } from '../../stateManagement/segmentation/segmentLocking';
import { getSegmentation } from '../../stateManagement/segmentation/getSegmentation';
import { getClosestImageIdForStackViewport } from '../../utilities/annotationHydration';
import { getCurrentLabelmapImageIdForViewport } from '../../stateManagement/segmentation/getCurrentLabelmapImageIdForViewport';
import { getSegmentIndexColor } from '../../stateManagement/segmentation/config/segmentationColor';
import { getActiveSegmentIndex } from '../../stateManagement/segmentation/getActiveSegmentIndex';
import { StrategyCallbacks } from '../../enums';
import * as LabelmapMemo from '../../utilities/segmentation/createLabelmapMemo';
import { getAllAnnotations, removeAnnotation, } from '../../stateManagement/annotation/annotationState';
import { filterAnnotationsForDisplay } from '../../utilities/planar';
import { isPointInsidePolyline3D } from '../../utilities/math/polyline';
import { triggerSegmentationDataModified } from '../../stateManagement/segmentation/triggerSegmentationEvents';
import { fillInsideCircle } from './strategies';
export default class LabelmapBaseTool extends BaseTool {
    static { this.previewData = {
        preview: null,
        element: null,
        timerStart: 0,
        timer: null,
        startPoint: [NaN, NaN],
        isDrag: false,
    }; }
    constructor(toolProps, defaultToolProps) {
        super(toolProps, defaultToolProps);
    }
    get _previewData() {
        return LabelmapBaseTool.previewData;
    }
    createMemo(segmentId, segmentationVoxelManager, preview) {
        this.memo ||= LabelmapMemo.createLabelmapMemo(segmentId, segmentationVoxelManager, preview);
        return this.memo;
    }
    createEditData(element) {
        const enabledElement = getEnabledElement(element);
        const { viewport } = enabledElement;
        const activeSegmentation = getActiveSegmentation(viewport.id);
        if (!activeSegmentation) {
            const event = new CustomEvent(Enums.Events.ERROR_EVENT, {
                detail: {
                    type: 'Segmentation',
                    message: 'No active segmentation detected, create a segmentation representation before using the brush tool',
                },
                cancelable: true,
            });
            eventTarget.dispatchEvent(event);
            return null;
        }
        const { segmentationId } = activeSegmentation;
        const segmentsLocked = getLockedSegmentIndices(segmentationId);
        const { representationData } = getSegmentation(segmentationId);
        const editData = this.getEditData({
            viewport,
            representationData,
            segmentsLocked,
            segmentationId,
        });
        return editData;
    }
    getEditData({ viewport, representationData, segmentsLocked, segmentationId, }) {
        if (viewport instanceof BaseVolumeViewport) {
            const { volumeId } = representationData[SegmentationRepresentations.Labelmap];
            const actors = viewport.getActors();
            const isStackViewport = viewport instanceof getClosestImageIdForStackViewport;
            if (isStackViewport) {
                const event = new CustomEvent(Enums.Events.ERROR_EVENT, {
                    detail: {
                        type: 'Segmentation',
                        message: 'Cannot perform brush operation on the selected viewport',
                    },
                    cancelable: true,
                });
                eventTarget.dispatchEvent(event);
                return null;
            }
            const volumes = actors.map((actorEntry) => cache.getVolume(actorEntry.referencedId));
            const segmentationVolume = cache.getVolume(volumeId);
            const referencedVolumeIdToThreshold = volumes.find((volume) => csUtils.isEqual(volume.dimensions, segmentationVolume.dimensions))?.volumeId || volumes[0]?.volumeId;
            return {
                volumeId,
                referencedVolumeId: this.configuration.threshold?.volumeId ??
                    referencedVolumeIdToThreshold,
                segmentsLocked,
            };
        }
        else {
            const segmentationImageId = getCurrentLabelmapImageIdForViewport(viewport.id, segmentationId);
            if (!segmentationImageId) {
                return;
            }
            return {
                imageId: segmentationImageId,
                segmentsLocked,
            };
        }
    }
    createHoverData(element, centerCanvas) {
        const enabledElement = getEnabledElement(element);
        const { viewport } = enabledElement;
        const camera = viewport.getCamera();
        const { viewPlaneNormal, viewUp } = camera;
        const viewportIdsToRender = [viewport.id];
        const { segmentIndex, segmentationId, segmentColor } = this.getActiveSegmentationData(viewport) || {};
        const brushCursor = {
            metadata: {
                viewPlaneNormal: [...viewPlaneNormal],
                viewUp: [...viewUp],
                FrameOfReferenceUID: viewport.getFrameOfReferenceUID(),
                referencedImageId: '',
                toolName: this.getToolName(),
                segmentColor,
            },
            data: {},
        };
        return {
            brushCursor,
            centerCanvas,
            segmentIndex,
            viewport,
            segmentationId,
            segmentColor,
            viewportIdsToRender,
        };
    }
    getActiveSegmentationData(viewport) {
        const viewportId = viewport.id;
        const activeRepresentation = getActiveSegmentation(viewportId);
        if (!activeRepresentation) {
            return;
        }
        const { segmentationId } = activeRepresentation;
        const segmentIndex = getActiveSegmentIndex(segmentationId);
        if (!segmentIndex) {
            return;
        }
        const segmentColor = getSegmentIndexColor(viewportId, segmentationId, segmentIndex);
        return {
            segmentIndex,
            segmentationId,
            segmentColor,
        };
    }
    getOperationData(element) {
        const editData = this._editData || this.createEditData(element);
        const { segmentIndex, segmentationId, brushCursor } = this._hoverData || this.createHoverData(element);
        const { data, metadata = {} } = brushCursor || {};
        const { viewPlaneNormal, viewUp } = metadata;
        const operationData = {
            ...editData,
            points: data?.handles?.points,
            segmentIndex,
            previewColors: this.configuration.preview?.enabled || this._previewData.preview
                ? this.configuration.preview?.previewColors
                : null,
            viewPlaneNormal,
            toolGroupId: this.toolGroupId,
            segmentationId,
            viewUp,
            activeStrategy: this.configuration.activeStrategy,
            configuration: this.configuration,
            preview: this._previewData?.preview,
            createMemo: this.createMemo.bind(this),
        };
        return operationData;
    }
    addPreview(element = this._previewData.element, options) {
        const { _previewData } = this;
        const acceptReject = options?.acceptReject;
        if (acceptReject === true) {
            this.acceptPreview(element);
        }
        else if (acceptReject === false) {
            this.rejectPreview(element);
        }
        const enabledElement = getEnabledElement(element);
        _previewData.preview = this.applyActiveStrategyCallback(enabledElement, this.getOperationData(element), StrategyCallbacks.AddPreview);
        _previewData.isDrag = true;
        return _previewData.preview;
    }
    rejectPreview(element = this._previewData.element) {
        if (!element || !this._previewData.preview) {
            return;
        }
        const enabledElement = getEnabledElement(element);
        this.applyActiveStrategyCallback(enabledElement, this.getOperationData(element), StrategyCallbacks.RejectPreview);
        this._previewData.preview = null;
        this._previewData.isDrag = false;
    }
    acceptPreview(element = this._previewData.element) {
        if (!element) {
            return;
        }
        this.doneEditMemo();
        const enabledElement = getEnabledElement(element);
        this.applyActiveStrategyCallback(enabledElement, this.getOperationData(element), StrategyCallbacks.AcceptPreview);
        this._previewData.isDrag = false;
        this._previewData.preview = null;
        this.doneEditMemo();
    }
    static viewportContoursToLabelmap(viewport, options) {
        const removeContours = options?.removeContours ?? true;
        const annotations = getAllAnnotations();
        const viewAnnotations = filterAnnotationsForDisplay(viewport, annotations);
        if (!viewAnnotations?.length) {
            return;
        }
        const contourAnnotations = viewAnnotations.filter((annotation) => annotation.data.contour?.polyline?.length);
        if (!contourAnnotations.length) {
            return;
        }
        const brushInstance = new LabelmapBaseTool({}, {
            configuration: {
                strategies: {
                    FILL_INSIDE_CIRCLE: fillInsideCircle,
                },
                activeStrategy: 'FILL_INSIDE_CIRCLE',
            },
        });
        const preview = brushInstance.addPreview(viewport.element);
        const { memo, segmentationId } = preview;
        const previewVoxels = memo?.voxelManager || preview.previewVoxelManager;
        const segmentationVoxels = previewVoxels.sourceVoxelManager || previewVoxels;
        const { dimensions } = previewVoxels;
        const imageData = viewport
            .getDefaultActor()
            .actor.getMapper()
            .getInputData();
        for (const annotation of contourAnnotations) {
            const boundsIJK = [
                [Infinity, -Infinity],
                [Infinity, -Infinity],
                [Infinity, -Infinity],
            ];
            const { polyline } = annotation.data.contour;
            for (const point of polyline) {
                const indexPoint = imageData.worldToIndex(point);
                indexPoint.forEach((v, idx) => {
                    boundsIJK[idx][0] = Math.min(boundsIJK[idx][0], v);
                    boundsIJK[idx][1] = Math.max(boundsIJK[idx][1], v);
                });
            }
            boundsIJK.forEach((bound, idx) => {
                bound[0] = Math.round(Math.max(0, bound[0]));
                bound[1] = Math.round(Math.min(dimensions[idx] - 1, bound[1]));
            });
            const activeIndex = getActiveSegmentIndex(segmentationId);
            const startPoint = annotation.data.handles?.[0] || polyline[0];
            const startIndex = imageData.worldToIndex(startPoint).map(Math.round);
            const startValue = segmentationVoxels.getAtIJKPoint(startIndex) || 0;
            let hasZeroIndex = false;
            let hasPositiveIndex = false;
            for (const polyPoint of polyline) {
                const polyIndex = imageData.worldToIndex(polyPoint).map(Math.round);
                const polyValue = segmentationVoxels.getAtIJKPoint(polyIndex);
                if (polyValue === startValue) {
                    hasZeroIndex = true;
                }
                else if (polyValue >= 0) {
                    hasPositiveIndex = true;
                }
            }
            const hasBoth = hasZeroIndex && hasPositiveIndex;
            const segmentIndex = hasBoth
                ? startValue
                : startValue === 0
                    ? activeIndex
                    : 0;
            for (let i = boundsIJK[0][0]; i <= boundsIJK[0][1]; i++) {
                for (let j = boundsIJK[1][0]; j <= boundsIJK[1][1]; j++) {
                    for (let k = boundsIJK[2][0]; k <= boundsIJK[2][1]; k++) {
                        const worldPoint = imageData.indexToWorld([i, j, k]);
                        const isContained = isPointInsidePolyline3D(worldPoint, polyline);
                        if (isContained) {
                            previewVoxels.setAtIJK(i, j, k, segmentIndex);
                        }
                    }
                }
            }
            if (removeContours) {
                removeAnnotation(annotation.annotationUID);
            }
        }
        const slices = previewVoxels.getArrayOfModifiedSlices();
        triggerSegmentationDataModified(segmentationId, slices);
    }
}
