import React, { useCallback, useEffect, useMemo, useState } from "react";
import { cloneDeep, isEmpty, isEqual, isFunction } from "lodash";
import { JSONConfiguration, JSONConverter } from "@deck.gl/json";
import DeckGL from "@deck.gl/react";
import { AmbientLight, _CameraLight as CameraLight, DirectionalLight, Layer, LightingEffect, OrbitController, OrbitView, OrthographicController, OrthographicView, PointLight, } from "@deck.gl/core";
import { LineLayer } from "@deck.gl/layers";
import { Matrix4 } from "@math.gl/core";
import { fovyToAltitude } from "@math.gl/web-mercator";
import { colorTables as defaultColorTables } from "@emerson-eps/color-tables";
import { validateColorTables, validateLayers } from "@webviz/wsc-common";
import { Axes2DLayer, NorthArrow3DLayer, WellsLayer } from "../layers";
import { getLayersByType, getModelMatrixScale, getWellLayerByTypeAndSelectedWells, } from "../layers/utils/layerTools";
import { isEmpty as isEmptyBox2D } from "../utils/BoundingBox2D";
import { boxCenter, boxUnion, isEmpty as isEmptyBox3D, } from "../utils/BoundingBox3D";
import JSON_CONVERTER_CONFIG from "../utils/configuration";
import { fitBounds } from "../utils/fitBounds";
import { DistanceScale } from "./DistanceScale";
import InfoCard from "./InfoCard";
import StatusIndicator from "./StatusIndicator";
import { getZoom, useLateralZoom } from "../utils/camera";
import { useScaleFactor, useShiftHeld } from "../utils/event";
import { defineController, useVerticalScale } from "../views/viewport";
import mergeRefs from "merge-refs";
import { WellLabelLayer } from "../layers/wells/layers/wellLabelLayer";
import { SectionView } from "../views/sectionView";
const minZoom3D = -12;
const maxZoom3D = 12;
const minZoom2D = -12;
const maxZoom2D = 12;
const DEFAULT_VIEWS = {
    layout: [1, 1],
    showLabel: false,
    marginPixels: 0,
    viewports: [{ id: "main-view", viewType: OrthographicView }],
};
function parseLights(lights) {
    var _a, _b, _c, _d;
    if (!lights) {
        return undefined;
    }
    const effects = [];
    const lightsObj = {};
    if (lights.ambientLight) {
        lightsObj["AmbientLight"] = new AmbientLight({
            intensity: lights.ambientLight.intensity,
            color: (_a = lights.ambientLight.color) !== null && _a !== void 0 ? _a : [255, 255, 255],
        });
    }
    if (lights.headLight) {
        lightsObj["HeadLight"] = new CameraLight({
            intensity: lights.headLight.intensity,
            color: (_b = lights.headLight.color) !== null && _b !== void 0 ? _b : [255, 255, 255],
        });
    }
    (_c = lights.pointLights) === null || _c === void 0 ? void 0 : _c.forEach((light, index) => {
        var _a;
        lightsObj[`PointLight_${index}`] = new PointLight(Object.assign(Object.assign({}, light), { color: (_a = light.color) !== null && _a !== void 0 ? _a : [255, 255, 255] }));
    });
    (_d = lights.directionalLights) === null || _d === void 0 ? void 0 : _d.forEach((light, index) => {
        var _a;
        lightsObj[`DirectionalLight_${index}`] = new DirectionalLight(Object.assign(Object.assign({}, light), { color: (_a = light.color) !== null && _a !== void 0 ? _a : [255, 255, 255] }));
    });
    const lightingEffect = new LightingEffect(lightsObj);
    effects.push(lightingEffect);
    return effects;
}
function mapBoundingBoxReducer(mapBoundingBox, action) {
    const union = boxUnion(mapBoundingBox, action.layerBoundingBox);
    return isEqual(union, mapBoundingBox) ? mapBoundingBox : union;
}
export function useHoverInfo() {
    const [hoverInfo, setHoverInfo] = useState([]);
    const callback = useCallback((pickEvent) => {
        setHoverInfo(pickEvent.infos);
    }, []);
    return [hoverInfo, callback];
}
// Helper function to handle deprecated "show3D" property of ViewportType
function show3D(viewPort) {
    if (typeof viewPort.show3D === "boolean") {
        console.warn("show3D is deprecated. Use viewType");
        return viewPort.show3D;
    }
    return viewPort.viewType === OrbitView;
}
function defaultTooltip(info) {
    var _a, _b, _c, _d, _e, _f, _g;
    if ((((_a = info.layer) === null || _a === void 0 ? void 0 : _a.constructor) === WellLabelLayer ||
        ((_b = info.sourceLayer) === null || _b === void 0 ? void 0 : _b.constructor) === WellLabelLayer) &&
        ((_c = info.object) === null || _c === void 0 ? void 0 : _c.wellLabels)) {
        return (_d = info.object.wellLabels) === null || _d === void 0 ? void 0 : _d.join("\n");
    }
    else if (info === null || info === void 0 ? void 0 : info.logName) {
        return info === null || info === void 0 ? void 0 : info.logName;
    }
    else if (((_e = info.layer) === null || _e === void 0 ? void 0 : _e.id) === "drawing-layer") {
        return (_f = info.propertyValue) === null || _f === void 0 ? void 0 : _f.toFixed(2);
    }
    const feat = info.object;
    return (_g = feat === null || feat === void 0 ? void 0 : feat.properties) === null || _g === void 0 ? void 0 : _g["name"];
}
function makeExtendedPickingInfo(info) {
    const scaledCoordinate = info.coordinate
        ? [...info.coordinate]
        : undefined;
    const extendedPickInfo = Object.assign(Object.assign({}, info), { scaledCoordinate });
    return extendedPickInfo;
}
const Map = (_a) => {
    var _b, _c, _d, _e, _f, _g, _h;
    var { id, layers, bounds, cameraPosition, triggerHome, views = DEFAULT_VIEWS, scale = { visible: true, cssStyle: { top: 10, left: 10 } }, coords, showReadout = (_b = coords === null || coords === void 0 ? void 0 : coords.visible) !== null && _b !== void 0 ? _b : true, pickingDepth = (_c = coords === null || coords === void 0 ? void 0 : coords.pickDepth) !== null && _c !== void 0 ? _c : 2, coordinateUnit = "m", colorTables = defaultColorTables, setEditedData, checkDatafileSchema = false, onMouseEvent, selection, children, getTooltip = defaultTooltip, getCameraPosition, onRenderingProgress, onDragStart, onDragEnd, onDrag, getCursor, lights, triggerResetMultipleWells, verticalScale, innerRef, pickingRadius, deckGlRef, } = _a;
    // From react doc, ref should not be read nor modified during rendering.
    const deckRef = React.useRef(null);
    React.useImperativeHandle(deckGlRef, () => deckRef.current);
    const [applyViewController, forceUpdate] = React.useReducer((x) => x + 1, 0);
    const viewController = useMemo(() => new ViewController(forceUpdate), []);
    // Extract the needed size from onResize function
    const [deckSize, setDeckSize] = useState({ width: 0, height: 0 });
    const onResize = useCallback((size) => {
        // exclude {0, 0} size (when rendered hidden pages)
        if (size.width > 0 && size.height > 0) {
            setDeckSize((prevSize) => {
                if ((prevSize === null || prevSize === void 0 ? void 0 : prevSize.width) !== size.width ||
                    (prevSize === null || prevSize === void 0 ? void 0 : prevSize.height) !== size.height) {
                    return size;
                }
                return prevSize;
            });
        }
    }, []);
    // 3d bounding box computed from the layers
    const [dataBoundingBox3d, dispatchBoundingBox] = React.useReducer(mapBoundingBoxReducer, undefined);
    // Get vertical scaling factor defined in viewports.
    const viewportVerticalScale = useVerticalScale(views === null || views === void 0 ? void 0 : views.viewports);
    // Used for scaling in z direction using arrow keys.
    const { factor: zReScale, elementRef: zScaleRef } = useScaleFactor();
    const { shiftHeld, divRef: shiftHeldRef } = useShiftHeld();
    // Prevent using internal hook for manipulating vertical scale when the vertical
    // scale is explicitly defined.
    const overrideVerticalScaling = verticalScale !== null && verticalScale !== void 0 ? verticalScale : viewportVerticalScale;
    const divRef = mergeRefs(overrideVerticalScaling ? undefined : zScaleRef, shiftHeldRef, innerRef);
    const zScale = (_d = verticalScale !== null && verticalScale !== void 0 ? verticalScale : viewportVerticalScale) !== null && _d !== void 0 ? _d : zReScale;
    // compute the viewport margins
    const viewPortMargins = React.useMemo(() => {
        var _a, _b;
        if (!(layers === null || layers === void 0 ? void 0 : layers.length)) {
            return {
                left: 0,
                right: 0,
                top: 0,
                bottom: 0,
            };
        }
        // Margins on the viewport are extracted from a potential axes2D layer.
        const axes2DLayer = layers === null || layers === void 0 ? void 0 : layers.find((e) => {
            return (e === null || e === void 0 ? void 0 : e.constructor) === Axes2DLayer;
        });
        const axes2DProps = axes2DLayer === null || axes2DLayer === void 0 ? void 0 : axes2DLayer.props;
        const marginV = (_a = axes2DProps === null || axes2DProps === void 0 ? void 0 : axes2DProps.minimalMarginV) !== null && _a !== void 0 ? _a : axes2DProps === null || axes2DProps === void 0 ? void 0 : axes2DProps.marginV;
        const marginH = (_b = axes2DProps === null || axes2DProps === void 0 ? void 0 : axes2DProps.minimalMarginH) !== null && _b !== void 0 ? _b : axes2DProps === null || axes2DProps === void 0 ? void 0 : axes2DProps.marginH;
        return {
            left: (axes2DProps === null || axes2DProps === void 0 ? void 0 : axes2DProps.isLeftRuler) ? marginH : 0,
            right: (axes2DProps === null || axes2DProps === void 0 ? void 0 : axes2DProps.isRightRuler) ? marginH : 0,
            top: (axes2DProps === null || axes2DProps === void 0 ? void 0 : axes2DProps.isTopRuler) ? marginV : 0,
            bottom: (axes2DProps === null || axes2DProps === void 0 ? void 0 : axes2DProps.isBottomRuler) ? marginV : 0,
        };
    }, [layers]);
    // selection
    useEffect(() => {
        var _a, _b, _c;
        const layers = (_b = (_a = deckRef.current) === null || _a === void 0 ? void 0 : _a.deck) === null || _b === void 0 ? void 0 : _b.props.layers;
        if (layers) {
            const wellslayer = (_c = getLayersByType(layers, WellsLayer.name)) === null || _c === void 0 ? void 0 : _c[0];
            wellslayer === null || wellslayer === void 0 ? void 0 : wellslayer.setSelection(selection === null || selection === void 0 ? void 0 : selection.well, selection === null || selection === void 0 ? void 0 : selection.selection);
        }
    }, [selection]);
    // multiple well layers
    const [multipleWells, setMultipleWells] = useState([]);
    const [selectedWell, setSelectedWell] = useState("");
    useEffect(() => {
        var _a, _b, _c;
        const layers = (_b = (_a = deckRef.current) === null || _a === void 0 ? void 0 : _a.deck) === null || _b === void 0 ? void 0 : _b.props.layers;
        if (layers) {
            const wellslayer = (_c = getWellLayerByTypeAndSelectedWells(layers, "WellsLayer", selectedWell)) === null || _c === void 0 ? void 0 : _c[0];
            wellslayer === null || wellslayer === void 0 ? void 0 : wellslayer.setMultiSelection(multipleWells);
        }
    }, [multipleWells, selectedWell]);
    useEffect(() => {
        if (typeof triggerResetMultipleWells !== "undefined") {
            setMultipleWells([]);
        }
    }, [triggerResetMultipleWells]);
    const getPickingInfos = useCallback((type, pickInfo, event) => {
        var _a;
        const depth = isFunction(pickingDepth)
            ? pickingDepth(type)
            : pickingDepth;
        if (depth < 0)
            throw new Error(`Expected a picking depth to be positive. Received ${depth}`);
        if (depth === 0)
            return [];
        if (!((_a = pickInfo.layer) === null || _a === void 0 ? void 0 : _a.context.deck)) {
            const extendedPickInfo = makeExtendedPickingInfo(pickInfo);
            // Z value should not take into account the Z scale factor.
            viewController.unscaledTarget(pickInfo.coordinate);
            return [extendedPickInfo];
        }
        const pickInfos = pickInfo.layer.context.deck.pickMultipleObjects({
            x: event.offsetCenter.x,
            y: event.offsetCenter.y,
            depth,
            unproject3D: true,
            radius: pickingRadius,
        });
        const extendedPickInfos = pickInfos.map((itemPick) => {
            var _a, _b, _c, _d;
            const extItemPick = makeExtendedPickingInfo(itemPick);
            // Z value should not take into account the Z scale factor.
            viewController.unscaledTarget(extItemPick.coordinate);
            if (extItemPick.layer instanceof WellsLayer) {
                const wellsPickInfo = extItemPick;
                const unit = (_c = (_b = (_a = extItemPick.layer) === null || _a === void 0 ? void 0 : _a.state.data) === null || _b === void 0 ? void 0 : _b.unit) !== null && _c !== void 0 ? _c : " ";
                (_d = wellsPickInfo.properties) === null || _d === void 0 ? void 0 : _d.forEach((property) => {
                    if (property.name.includes("MD") ||
                        property.name.includes("TVD")) {
                        const fixedNumber = Number(property.value).toFixed(2);
                        property.value = `${fixedNumber} ${unit}`;
                    }
                });
            }
            return extItemPick;
        });
        return extendedPickInfos;
    }, [pickingDepth, pickingRadius, viewController]);
    /**
     * call onMouseEvent callback
     */
    const callOnMouseEvent = useCallback((type, infos, event) => {
        if (event.leftButton &&
            // eslint-disable-next-line @typescript-eslint/ban-ts-comment
            // @ts-expect-error
            event.tapCount == 2 // Note. Detect double click.
        ) {
            // Left button click identifies new camera rotation anchor.
            if (infos.length >= 1 && infos[0].coordinate) {
                viewController.setScaledTarget(infos[0].scaledCoordinate);
            }
        }
        if (!onMouseEvent)
            return;
        const ev = handleMouseEvent(type, infos, event);
        onMouseEvent(ev);
    }, [onMouseEvent, viewController]);
    // eslint-disable-next-line @typescript-eslint/no-explicit-any
    const [hoverInfo, setHoverInfo] = useState([]);
    const onHover = useCallback((pickInfo, event) => {
        const infos = getPickingInfos("hover", pickInfo, event);
        setHoverInfo(infos);
        callOnMouseEvent === null || callOnMouseEvent === void 0 ? void 0 : callOnMouseEvent("hover", infos, event);
    }, [callOnMouseEvent, getPickingInfos]);
    const onClick = useCallback((pickInfo, event) => {
        const infos = getPickingInfos("click", pickInfo, event);
        callOnMouseEvent === null || callOnMouseEvent === void 0 ? void 0 : callOnMouseEvent("click", infos, event);
    }, [callOnMouseEvent, getPickingInfos]);
    const deckGLLayers = React.useMemo(() => {
        if (!layers) {
            return [];
        }
        if (layers.length === 0) {
            // Empty layers array makes deck.gl set deckRef to undefined (no OpenGL context).
            // Hence insert dummy layer.
            const dummy_layer = new LineLayer({
                id: "webviz_internal_dummy_layer",
                visible: false,
            });
            layers.push(dummy_layer);
        }
        return layers.map((item) => {
            if ((item === null || item === void 0 ? void 0 : item.constructor.name) === NorthArrow3DLayer.name) {
                return item;
            }
            let m;
            if (item.props.modelMatrix) {
                m = item.props.modelMatrix;
                m[10] *= zScale; // Z scaling element of matrix.
            }
            else {
                m = getModelMatrixScale(zScale);
            }
            return item.clone({
                // Inject "dispatchBoundingBox" function into layer for it to report back its respective bounding box.
                // eslint-disable-next-line
                // @ts-ignore
                reportBoundingBox: dispatchBoundingBox,
                // Modify "modelMatrix" to reflect correct z scaling.
                modelMatrix: m,
            });
        });
    }, [layers, zScale]);
    const [loadingProgress, setLoadingProgress] = useState(0);
    const onAfterRender = useCallback(() => {
        var _a, _b;
        if (deckGLLayers) {
            let progress = 100;
            // get all the layers ids used in the views, to consider only visible layers
            const layersIdsPresentInViews = (_b = (_a = views === null || views === void 0 ? void 0 : views.viewports) === null || _a === void 0 ? void 0 : _a.map((viewport) => viewport === null || viewport === void 0 ? void 0 : viewport.layerIds)) === null || _b === void 0 ? void 0 : _b.flat();
            const someViewportHasUndefinedLayerIds = layersIdsPresentInViews === null || layersIdsPresentInViews === void 0 ? void 0 : layersIdsPresentInViews.includes(undefined);
            const allViewportsHaveAnEmptyListOfLayerIds = !someViewportHasUndefinedLayerIds &&
                (layersIdsPresentInViews === null || layersIdsPresentInViews === void 0 ? void 0 : layersIdsPresentInViews.length) === 0;
            const emptyLayers = // There will always be a dummy layer. Deck.gl does not like empty array of layers.
             deckGLLayers.length === 1 &&
                deckGLLayers[0].id ===
                    "webviz_internal_dummy_layer";
            if (!emptyLayers && !allViewportsHaveAnEmptyListOfLayerIds) {
                // compute #done layers / #visible layers percentage
                const visibleLayers = deckGLLayers.filter((layer) => {
                    var _a;
                    const layerWithType = layer;
                    const filterByLayersInViews = !someViewportHasUndefinedLayerIds
                        ? layersIdsPresentInViews === null || layersIdsPresentInViews === void 0 ? void 0 : layersIdsPresentInViews.includes(layerWithType.id)
                        : true;
                    return (((_a = layerWithType === null || layerWithType === void 0 ? void 0 : layerWithType.props) === null || _a === void 0 ? void 0 : _a.visible) && filterByLayersInViews);
                });
                const loaded = visibleLayers === null || visibleLayers === void 0 ? void 0 : visibleLayers.filter((layer) => layer === null || layer === void 0 ? void 0 : layer.isLoaded).length;
                // ceil to ensure reaching 100 (important to check if done)
                progress = Math.ceil((100 * loaded) / (visibleLayers === null || visibleLayers === void 0 ? void 0 : visibleLayers.length));
            }
            setLoadingProgress(progress);
            if (onRenderingProgress) {
                onRenderingProgress(progress);
            }
        }
    }, [deckGLLayers, onRenderingProgress, views === null || views === void 0 ? void 0 : views.viewports]);
    // validate layers data
    const [errorText, setErrorText] = useState();
    useEffect(() => {
        var _a, _b;
        const layers = (_b = (_a = deckRef.current) === null || _a === void 0 ? void 0 : _a.deck) === null || _b === void 0 ? void 0 : _b.props.layers;
        // this ensures to validate the schemas only once
        if (checkDatafileSchema && layers && loadingProgress === 100) {
            try {
                validateLayers(layers);
                // TODO: Fix this the next time the file is edited.
                // eslint-disable-next-line @typescript-eslint/no-unused-expressions
                colorTables && validateColorTables(colorTables);
            }
            catch (e) {
                setErrorText(String(e));
            }
        }
        else
            setErrorText(undefined);
    }, [
        checkDatafileSchema,
        colorTables,
        (_f = (_e = deckRef === null || deckRef === void 0 ? void 0 : deckRef.current) === null || _e === void 0 ? void 0 : _e.deck) === null || _f === void 0 ? void 0 : _f.props.layers,
        loadingProgress,
    ]);
    const layerFilter = useCallback((args) => {
        // display all the layers if views are not specified correctly
        if (!(views === null || views === void 0 ? void 0 : views.viewports) || !(views === null || views === void 0 ? void 0 : views.layout)) {
            return true;
        }
        const cur_view = views.viewports.find(({ id }) => args.viewport.id && id === args.viewport.id);
        if ((cur_view === null || cur_view === void 0 ? void 0 : cur_view.layerIds) === undefined) {
            return true;
        }
        if ((cur_view === null || cur_view === void 0 ? void 0 : cur_view.layerIds) && cur_view.layerIds.length > 0) {
            const layer_ids = cur_view.layerIds;
            return layer_ids.some((layer_id) => {
                const t = layer_id === args.layer.id;
                return t;
            });
        }
        else {
            return false;
        }
    }, [views]);
    const onViewStateChange = useCallback(
    // eslint-disable-next-line @typescript-eslint/no-explicit-any
    ({ viewId, viewState }) => viewController.onViewStateChange(viewId, viewState, getCameraPosition), [getCameraPosition, viewController]);
    const getCursorFunc = useCallback(function getCursorFunc(cursorState) {
        if (getCursor) {
            return getCursor(cursorState);
        }
        return cursorState.isDragging ? "grabbing" : "default";
    }, [getCursor]);
    const effects = (_g = parseLights(lights)) !== null && _g !== void 0 ? _g : [];
    const [deckGlViews, deckGlViewState] = useMemo(() => {
        const state = {
            triggerHome,
            camera: cameraPosition,
            bounds,
            boundingBox3d: dataBoundingBox3d,
            viewPortMargins,
            deckSize,
            zScale,
        };
        return viewController.getViews(views, state);
        // eslint-disable-next-line react-hooks/exhaustive-deps
    }, [
        triggerHome,
        cameraPosition,
        bounds,
        dataBoundingBox3d,
        viewPortMargins,
        deckSize,
        views,
        zScale,
        applyViewController,
        viewController,
    ]);
    const lateralZoom = useLateralZoom(deckGlViewState);
    if (!deckGlViews || isEmpty(deckGlViews) || isEmpty(deckGLLayers)) {
        return null;
    }
    return (React.createElement("div", { ref: divRef, onContextMenu: (event) => event.preventDefault() },
        React.createElement(DeckGL, { id: id, viewState: deckGlViewState, views: deckGlViews, layerFilter: layerFilter, layers: deckGLLayers, 
            // eslint-disable-next-line @typescript-eslint/ban-ts-comment
            // @ts-expect-error
            userData: {
                setEditedData: (updated_prop) => {
                    setSelectedWell(updated_prop["selectedWell"]);
                    if (Object.keys(updated_prop).includes("selectedWell")) {
                        if (shiftHeld) {
                            if (multipleWells.includes(updated_prop["selectedWell"])) {
                                const temp = multipleWells.filter((item) => item !==
                                    updated_prop["selectedWell"]);
                                setMultipleWells(temp);
                            }
                            else {
                                const temp = multipleWells.concat(updated_prop["selectedWell"]);
                                setMultipleWells(temp);
                            }
                        }
                        else {
                            setMultipleWells([]);
                        }
                    }
                    setEditedData === null || setEditedData === void 0 ? void 0 : setEditedData(updated_prop);
                },
                colorTables: colorTables,
            }, getCursor: getCursorFunc, getTooltip: getTooltip, ref: deckRef, onViewStateChange: onViewStateChange, onHover: onHover, onClick: onClick, onAfterRender: onAfterRender, effects: effects, onDragStart: onDragStart, onDragEnd: onDragEnd, onDrag: onDrag, onResize: onResize, pickingRadius: pickingRadius }, children),
        (scale === null || scale === void 0 ? void 0 : scale.visible) ? (React.createElement(DistanceScale, Object.assign({}, scale, { zoom: lateralZoom, scaleUnit: coordinateUnit, style: (_h = scale.cssStyle) !== null && _h !== void 0 ? _h : {} }))) : null,
        !onRenderingProgress && loadingProgress < 100 && (React.createElement("div", { style: {
                display: "flex",
                alignItems: "flex-end",
                justifyContent: "right",
                position: "absolute",
                height: "10%",
                width: "10%",
                bottom: "10px",
                right: "10px",
                zIndex: 200,
            } },
            React.createElement(StatusIndicator, { progress: loadingProgress, label: "Loading assets..." }))),
        showReadout && React.createElement(InfoCard, { pickInfos: hoverInfo }),
        errorText && (React.createElement("pre", { style: {
                flex: "0, 0",
                color: "rgb(255, 64, 64)",
                backgroundColor: "rgb(255, 255, 192)",
            } }, errorText))));
};
export default Map;
// ------------- Helper functions ---------- //
// Add the resources as an enum in the Json Configuration and then convert the spec to actual objects.
// See https://deck.gl/docs/api-reference/json/overview for more details.
export function jsonToObject(data, enums = undefined) {
    if (!data)
        return [];
    const configuration = createConfiguration(enums);
    const jsonConverter = new JSONConverter({ configuration });
    // remove empty data/layer object
    const filtered_data = data.filter((value) => !isEmpty(value));
    return jsonConverter.convert(filtered_data);
}
/**
 * Creates layers according to layer definiton Records.
 * Keeps the relative order of layers.
 * @param layers Array of layer definition Records or externally created layers.
 * @returns Array of layer instances.
 */
export function createLayers(layers, enums = undefined) {
    const configuration = createConfiguration(enums);
    // remove empty record/layer objects
    const filtered_layers = layers.filter((value) => !isEmpty(value));
    const layersList = new Array(filtered_layers.length);
    const jsonLayerIndices = [];
    const jsonLayerDefs = [];
    filtered_layers.forEach((layer, index) => {
        if (layer instanceof Layer) {
            layersList[index] = layer;
        }
        else if (layer["@@typedArraySupport"]) {
            layersList[index] = createLayer(layer, configuration);
        }
        else {
            jsonLayerDefs.push(layer);
            jsonLayerIndices.push(index);
        }
    });
    const jsonLayers = jsonToObject(jsonLayerDefs, enums);
    jsonLayers.forEach((layer, index) => {
        const layerIndex = jsonLayerIndices[index];
        layersList[layerIndex] = layer;
    });
    return layersList.filter((value) => !isEmpty(value));
}
function createConfiguration(enums = undefined) {
    const configuration = new JSONConfiguration(JSON_CONVERTER_CONFIG);
    enums === null || enums === void 0 ? void 0 : enums.forEach((enumeration) => {
        if (enumeration) {
            configuration.merge({
                enumerations: Object.assign({}, enumeration),
            });
        }
    });
    return configuration;
}
function createLayer(layerData, configuration) {
    const typeKey = configuration.typeKey;
    // eslint-disable-next-line @typescript-eslint/no-explicit-any
    const classes = configuration.classes;
    if (layerData[typeKey]) {
        const type = layerData[typeKey];
        if (type in classes) {
            const Class = classes[type];
            // Prepare a props object
            const props = Object.assign({}, layerData);
            delete props[typeKey];
            return new Class(props);
        }
    }
    return null;
}
/**
 * The `ViewController` class manages the state and interactions for a 3D view.
 * It handles the rendering, view state updates, and synchronization of multiple views.
 *
 * @classdesc This class is responsible for managing the state of a 3D view, including
 *            camera settings, viewports, and interaction readiness. It provides methods
 *            to set targets, get views, and handle view state changes.
 */
class ViewController {
    /**
     * Constructs a new instance of the Map component.
     *
     * @param rerender - A function to trigger a re-render of the component.
     */
    constructor(rerender) {
        /**
         * Derived state for interaction readiness and view state changes.
         */
        this.derivedState_ = {
            readyForInteraction: false,
            viewStateChanged: false,
        };
        /**
         * Full state including camera settings, bounds, and deck size.
         */
        this.state_ = Object.assign({ triggerHome: undefined, camera: undefined, bounds: undefined, boundingBox3d: undefined, deckSize: { width: 0, height: 0 }, zScale: 1, viewPortMargins: {
                left: 0,
                right: 0,
                top: 0,
                bottom: 0,
            } }, this.derivedState_);
        /**
         * The current views being managed.
         */
        this.views_ = undefined;
        /**
         * The result object containing views and view states.
         */
        this.result_ = {
            views: [],
            deckglViewStates: {},
            viewStates: {},
        };
        this.rerender_ = rerender;
    }
    /**
     * Sets the target from picks, which comes from the displayed scaled data.
     * @param scaledTarget scaled 3D point.
     */
    setScaledTarget(scaledTarget) {
        if (!scaledTarget) {
            return console.warn("Undefined scale-target! Skipping...");
        }
        const vsKey = Object.keys(this.result_.deckglViewStates).at(0);
        if (vsKey) {
            // deep clone to notify change (memo checks object address)
            this.result_.deckglViewStates = cloneDeep(this.result_.deckglViewStates);
            // update target of deckglViewStates with the scaled event target
            this.result_.deckglViewStates[vsKey].target = scaledTarget;
            this.result_.deckglViewStates[vsKey].transitionDuration = 1000;
            // update target of deckglViewStates with the scaled event target
            this.result_.viewStates[vsKey].target = inversedZScaled(scaledTarget, this.state_.zScale, this.result_.viewStates[vsKey].target);
            this.rerender_();
        }
    }
    unscaledTarget(target) {
        applyInverseZScale(target, this.state_.zScale);
    }
    /**
     * Retrieves the views and their corresponding view states to be sent to DeckGL.
     * @param views - The requested views.
     * @param state - The current state.
     * @returns The new views and their corresponding view states.
     */
    getViews(views, state) {
        const fullState = this.consolidateState(state);
        const newDeckglViews = this.getDeckGlViews(views, fullState);
        const [newDeckglViewState, newViewStates] = this.getDeckGlAndUserViewStates(views, fullState);
        // do not update this.state_ as it has not yet been applied
        if (!isEmpty(newDeckglViewState)) {
            this.state_ = fullState;
        }
        this.views_ = views;
        this.result_.views = newDeckglViews;
        this.result_.deckglViewStates = newDeckglViewState;
        this.result_.viewStates = newViewStates;
        return [newDeckglViews, newDeckglViewState];
    }
    /**
     * Consolidates the controlled state (ie. set by parent) with the uncontrolled state.
     * @param state - The current state.
     * @returns The consolidated state.
     */
    consolidateState(state) {
        return Object.assign(Object.assign({}, state), this.derivedState_);
    }
    /**
     * Returns the DeckGL views (ie. view position and viewport) based on the input views and current state.
     * @param views - The requested views.
     * @param state - The current state.
     * @returns The DeckGL views.
     */
    getDeckGlViews(views, state) {
        const needUpdate = views != this.views_ || state.deckSize != this.state_.deckSize;
        if (!needUpdate) {
            return this.result_.views;
        }
        return buildDeckGlViews(views, state.deckSize);
    }
    /**
     * Returns the scaled DeckGL view state(s) (ie. camera settings applied to individual views)
     * and the corresponding view states in user space (ie. not scaled by the zScale)
     * based on the input views and current state.
     * @param views requested views.
     * @param state current state.
     * @returns The DeckGL view states and the corresponding view states in user space.
     */
    getDeckGlAndUserViewStates(views, state) {
        var _a;
        const viewsChanged = views != this.views_;
        const triggerHome = state.triggerHome !== this.state_.triggerHome;
        const updateZScale = viewsChanged || state.zScale !== ((_a = this.state_) === null || _a === void 0 ? void 0 : _a.zScale) || triggerHome;
        const updateViewState = viewsChanged ||
            triggerHome ||
            state.camera != this.state_.camera ||
            state.bounds != this.state_.bounds ||
            (!state.viewStateChanged &&
                (state.boundingBox3d !== this.state_.boundingBox3d ||
                    state.deckSize != this.state_.deckSize));
        const needUpdate = updateZScale || updateViewState;
        const isCacheEmpty = isEmpty(this.result_.deckglViewStates);
        if (!isCacheEmpty && !needUpdate) {
            return [this.result_.deckglViewStates, this.result_.viewStates];
        }
        // initialize with last result
        const prevDeckglViewStates = this.result_.deckglViewStates;
        let viewStates = this.result_.viewStates;
        let deckglViewStates = this.result_.deckglViewStates;
        if (updateViewState || isCacheEmpty) {
            viewStates = buildViewStates(views, state.viewPortMargins, state.camera, state.boundingBox3d, state.bounds, state.deckSize);
            // create corresponding scaled states, to be handed over to DeckGL
            deckglViewStates = buildScaledViewStates(viewStates, state.zScale);
            // reset state
            this.derivedState_.readyForInteraction = canCameraBeDefined(state.camera, state.boundingBox3d, state.bounds, state.deckSize);
            this.derivedState_.viewStateChanged = false;
            return [deckglViewStates, viewStates];
        }
        // check if view state could be computed
        if (isEmpty(viewStates)) {
            return [deckglViewStates, viewStates];
        }
        if (updateZScale) {
            // deep clone to notify change (memo checks object address)
            if (deckglViewStates === prevDeckglViewStates) {
                deckglViewStates = cloneDeep(prevDeckglViewStates);
            }
            // Z scale to apply to target.
            // update target
            for (const key in deckglViewStates) {
                if (deckglViewStates[key].target) {
                    deckglViewStates[key].target = zScaledTarget(viewStates[key].target, state.zScale);
                }
            }
        }
        return [deckglViewStates, viewStates];
    }
    /**
     * Handles changes to the view state.
     * @param viewId - The ID of the view.
     * @param viewState - The new view state.
     * @param getCameraPosition - A function to get the camera position.
     */
    onViewStateChange(viewId, viewState, getCameraPosition) {
        var _a, _b, _c, _d, _e, _f;
        if (!this.derivedState_.readyForInteraction) {
            // disable interaction if the camera is not defined
            return;
        }
        const viewports = (_b = (_a = this.views_) === null || _a === void 0 ? void 0 : _a.viewports) !== null && _b !== void 0 ? _b : [];
        if (((_c = viewState.target) === null || _c === void 0 ? void 0 : _c.length) === 2) {
            // In orthographic mode viewState.target contains only x and y. Add existing z value.
            viewState.target.push((_e = (_d = this.result_.deckglViewStates[viewId].target) === null || _d === void 0 ? void 0 : _d[2]) !== null && _e !== void 0 ? _e : 1);
        }
        const isSyncIds = viewports
            .filter((item) => item.isSync)
            .map((item) => item.id);
        if (isSyncIds === null || isSyncIds === void 0 ? void 0 : isSyncIds.includes(viewId)) {
            const syncedViewStates = (_f = this.views_) === null || _f === void 0 ? void 0 : _f.viewports.filter((item) => item.isSync).map((item) => [item.id, viewState]);
            const tempViewStates = Object.fromEntries(syncedViewStates !== null && syncedViewStates !== void 0 ? syncedViewStates : []);
            this.result_.deckglViewStates = Object.assign(Object.assign({}, this.result_.deckglViewStates), tempViewStates);
            // update corresponding view state
            const keys = Object.keys(tempViewStates);
            keys.forEach((key) => {
                this.result_.viewStates = Object.assign(Object.assign({}, this.result_.viewStates), { [key]: Object.assign(Object.assign({}, tempViewStates[key]), { target: inversedZScaled(tempViewStates[key].target, this.state_.zScale, this.result_.viewStates[key].target) }) });
            });
        }
        else {
            this.result_.deckglViewStates = Object.assign(Object.assign({}, this.result_.deckglViewStates), { [viewId]: viewState });
            // update corresponding view state
            this.result_.viewStates = Object.assign(Object.assign({}, this.result_.viewStates), { [viewId]: Object.assign(Object.assign({}, viewState), { target: inversedZScaled(viewState.target, this.state_.zScale, this.result_.viewStates[viewId].target) }) });
        }
        if (getCameraPosition) {
            getCameraPosition(this.result_.viewStates[viewId]);
        }
        this.derivedState_.viewStateChanged = true;
        this.rerender_();
    }
}
/**
 * Returns the zoom factor allowing to view the complete boundingBox.
 * @param camera camera defining the view orientation.
 * @param boundingBox 3D bounding box to visualize.
 * @param size widget size.
 * @param fov field of view (see deck.gl file orbit-viewports.ts).
 */
function computeCameraZoom(camera, boundingBox, size, fovy = 50) {
    // constants and camera constants
    const DEGREES_TO_RADIANS = Math.PI / 180;
    const RADIANS_TO_DEGREES = 180 / Math.PI;
    const IDENTITY = [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1];
    const fD = fovyToAltitude(fovy);
    const cameraFovVertical = 50;
    const angle_ver = (cameraFovVertical / 2) * DEGREES_TO_RADIANS;
    const L = size.height / 2 / Math.sin(angle_ver);
    const r = L * Math.cos(angle_ver);
    const cameraFov = 2 * Math.atan(size.width / 2 / r) * RADIANS_TO_DEGREES;
    const angle_hor = (cameraFov / 2) * DEGREES_TO_RADIANS;
    const fwX = fD * Math.tan(angle_hor);
    const fwY = fD * Math.tan(angle_ver);
    const m = new Matrix4(IDENTITY);
    m.rotateX(camera.rotationX * DEGREES_TO_RADIANS);
    m.rotateZ(camera.rotationOrbit * DEGREES_TO_RADIANS);
    // end of constants and camera constants
    const xMin = boundingBox[0];
    const yMin = boundingBox[1];
    const zMin = boundingBox[2];
    const xMax = boundingBox[3];
    const yMax = boundingBox[4];
    const zMax = boundingBox[5];
    const target = [
        xMin + (xMax - xMin) / 2,
        yMin + (yMax - yMin) / 2,
        zMin + (zMax - zMin) / 2,
    ];
    const points = [
        [xMin, yMin, zMin],
        [xMin, yMax, zMin],
        [xMax, yMax, zMin],
        [xMax, yMin, zMin],
        [xMin, yMin, zMax],
        [xMin, yMax, zMax],
        [xMax, yMax, zMax],
        [xMax, yMin, zMax],
    ];
    let zoom = 999;
    for (const point of points) {
        const x_ = (point[0] - target[0]) / size.height;
        const y_ = (point[1] - target[1]) / size.height;
        const z_ = (point[2] - target[2]) / size.height;
        const [x, y, z] = m.transformAsVector([x_, y_, z_]);
        if (y >= 0) {
            // These points will actually appear further away when zooming in.
            continue;
        }
        let y_new = fwX / (Math.abs(x) / y - fwX / fD);
        const zoom_x = Math.log2(y_new / y);
        y_new = fwY / (Math.abs(z) / y - fwY / fD);
        const zoom_z = Math.log2(y_new / y);
        // it needs to be inside view volume in both directions.
        zoom = zoom_x < zoom ? zoom_x : zoom;
        zoom = zoom_z < zoom ? zoom_z : zoom;
    }
    return zoom;
}
///////////////////////////////////////////////////////////////////////////////////////////
// return viewstate with computed bounds to fit the data in viewport
function getViewStateFromBounds(viewPortMargins, bounds_accessor, target, views, viewPort, size) {
    var _a, _b;
    const bounds = typeof bounds_accessor == "function"
        ? bounds_accessor()
        : bounds_accessor;
    let w = bounds[2] - bounds[0]; // right - left
    let h = bounds[3] - bounds[1]; // top - bottom
    const z = target[2];
    const fb = fitBounds({ width: w, height: h, bounds });
    let fb_target = [fb.x, fb.y, z];
    let fb_zoom = fb.zoom;
    if (size.width > 0 && size.height > 0) {
        // If there are margins/rulers in the viewport (axes2DLayer) we have to account for that.
        // Camera target should be in the middle of viewport minus the rulers.
        const w_bounds = w;
        const h_bounds = h;
        const ml = viewPortMargins.left;
        const mr = viewPortMargins.right;
        const mb = viewPortMargins.bottom;
        const mt = viewPortMargins.top;
        // Subtract margins.
        const marginH = (ml > 0 ? ml : 0) + (mr > 0 ? mr : 0);
        const marginV = (mb > 0 ? mb : 0) + (mt > 0 ? mt : 0);
        w = size.width - marginH; // width of the viewport minus margin.
        h = size.height - marginV;
        // Special case if matrix views.
        // Use width and height for a sub-view instead of full viewport.
        if (views === null || views === void 0 ? void 0 : views.layout) {
            const [nY, nX] = views.layout;
            const isMatrixViews = nX !== 1 || nY !== 1;
            if (isMatrixViews) {
                const mPixels = (_a = views === null || views === void 0 ? void 0 : views.marginPixels) !== null && _a !== void 0 ? _a : 0;
                const w_ = 99.5 / nX; // Using 99.5% of viewport to avoid flickering of deckgl canvas
                const h_ = 99.5 / nY;
                const marginHorPercentage = 100 * 100 * (mPixels / (w_ * size.width)); //percentage of sub view
                const marginVerPercentage = 100 * 100 * (mPixels / (h_ * size.height));
                const sub_w = (w_ / 100) * size.width;
                const sub_h = (h_ / 100) * size.height;
                w = sub_w * (1 - 2 * (marginHorPercentage / 100)) - marginH;
                h = sub_h * (1 - 2 * (marginVerPercentage / 100)) - marginV;
            }
        }
        const port_aspect = h / w;
        const bounds_aspect = h_bounds / w_bounds;
        const m_pr_pixel = bounds_aspect > port_aspect ? h_bounds / h : w_bounds / w;
        let translate_x = 0;
        if (ml > 0 && mr === 0) {
            // left margin and no right margin
            translate_x = 0.5 * ml * m_pr_pixel;
        }
        else if (ml === 0 && mr > 0) {
            // no left margin but  right margin
            translate_x = -0.5 * mr * m_pr_pixel;
        }
        let translate_y = 0;
        if (mb > 0 && mt === 0) {
            translate_y = 0.5 * mb * m_pr_pixel;
        }
        else if (mb === 0 && mt > 0) {
            translate_y = -0.5 * mt * m_pr_pixel;
        }
        const fb = fitBounds({ width: w, height: h, bounds });
        fb_target = [fb.x - translate_x, fb.y - translate_y, z];
        fb_zoom = fb.zoom;
    }
    // For large numbers (like utm coordinates) max zoom is limited in 2D to avoid numerical imprecision.
    const isLarge = Math.max(bounds[0], bounds[1]) > 99999;
    const max2DZoom = isLarge ? 3 : maxZoom2D;
    const view_state = {
        target: (_b = viewPort.target) !== null && _b !== void 0 ? _b : fb_target,
        zoom: getZoom(viewPort, fb_zoom),
        rotationX: 90, // look down z -axis
        rotationOrbit: 0,
        minZoom: show3D(viewPort) ? minZoom3D : minZoom2D,
        maxZoom: show3D(viewPort) ? maxZoom3D : max2DZoom,
    };
    return view_state;
}
///////////////////////////////////////////////////////////////////////////////////////////
// build views
function getViewType(viewport) {
    // 3D views always use OrbitView with OrbitController
    if (show3D(viewport)) {
        return [OrbitView, OrbitController];
    }
    // Handle specific viewType cases
    if (viewport.viewType) {
        switch (viewport.viewType) {
            case SectionView:
                return [SectionView, OrthographicController];
            case OrthographicView:
                return [OrthographicView, OrthographicController];
            default:
                return [OrbitView, OrbitController];
        }
    }
    // Default 2D behavior based on viewport ID
    return [OrthographicView, OrthographicController];
}
function areViewsValid(views, size) {
    var _a, _b;
    const isInvalid = (views === null || views === void 0 ? void 0 : views.viewports) == undefined ||
        (views === null || views === void 0 ? void 0 : views.layout) == undefined ||
        !((_a = views === null || views === void 0 ? void 0 : views.layout) === null || _a === void 0 ? void 0 : _a[0]) ||
        !((_b = views === null || views === void 0 ? void 0 : views.layout) === null || _b === void 0 ? void 0 : _b[1]) ||
        !size.width ||
        !size.height;
    return !isInvalid;
}
/** returns a new View instance. */
function newView(viewport, x, y, width, height) {
    const far = 9999;
    const near = show3D(viewport) ? 0.1 : -1000;
    const [ViewType, Controller] = getViewType(viewport);
    return new ViewType({
        id: viewport.id,
        controller: defineController(Controller, viewport === null || viewport === void 0 ? void 0 : viewport.controller),
        x,
        y,
        width,
        height,
        flipY: false,
        far,
        near,
    });
}
function buildDeckGlViews(views, size) {
    var _a;
    const isOk = areViewsValid(views, size);
    if (!views || !isOk) {
        return [
            new OrthographicView({
                id: "main",
                controller: null,
                x: "0%",
                y: "0%",
                width: "100%",
                height: "100%",
                flipY: false,
                far: +9999,
                near: -1000,
            }),
        ];
    }
    // compute
    const [nY, nX] = views.layout;
    // compute for single view (code is more readable)
    const singleView = nX === 1 && nY === 1;
    if (singleView) {
        // Using 99.5% of viewport to avoid flickering of deckgl canvas
        return [newView(views.viewports[0], 0, 0, "99.5%", "99.5%")];
    }
    // compute for matrix
    const result = [];
    const w = 99.5 / nX; // Using 99.5% of viewport to avoid flickering of deckgl canvas
    const h = 99.5 / nY;
    const marginPixels = (_a = views.marginPixels) !== null && _a !== void 0 ? _a : 0;
    const marginHorPercentage = 100 * 100 * (marginPixels / (w * size.width));
    const marginVerPercentage = 100 * 100 * (marginPixels / (h * size.height));
    let yPos = 0;
    for (let y = 1; y <= nY; y++) {
        let xPos = 0;
        for (let x = 1; x <= nX; x++) {
            if (result.length >= views.viewports.length) {
                // stop when all the viewports are filled
                return result;
            }
            const currentViewport = views.viewports[result.length];
            const viewX = xPos + marginHorPercentage / nX + "%";
            const viewY = yPos + marginVerPercentage / nY + "%";
            const viewWidth = w * (1 - 2 * (marginHorPercentage / 100)) + "%";
            const viewHeight = h * (1 - 2 * (marginVerPercentage / 100)) + "%";
            result.push(newView(currentViewport, viewX, viewY, viewWidth, viewHeight));
            xPos = xPos + w;
        }
        yPos = yPos + h;
    }
    return result;
}
/**
 * Returns true if the camera zoom is set.
 * @param camera to be tested
 * @returns true if the camera camera zoom is set.
 */
function cameraHasZoom(camera) {
    const zoom = camera === null || camera === void 0 ? void 0 : camera.zoom;
    const isNumber = typeof zoom === "number" && !Number.isNaN(zoom);
    const isXy = Array.isArray(zoom) && zoom.length == 2;
    return isNumber || isXy;
}
/**
 * Returns true if the camera target is set.
 * @param camera to be tested
 * @returns true if the camera camera target is set.
 */
function cameraHasTarget(camera) {
    return (Array.isArray(camera === null || camera === void 0 ? void 0 : camera.target) &&
        camera.target.length >= 2 &&
        camera.target.length <= 3);
}
/**
 * Returns true if the camera is fully defined.
 * This ensures that the corresponding projection matrix is inversible.
 * @param camera to be tested
 * @returns true if the camera is fully defined.
 */
function isCameraDefined(camera) {
    return cameraHasZoom(camera) && cameraHasTarget(camera);
}
/**
 * Returns true if the camera is fully defined.
 * This ensures that the corresponding projection matrix is inversible.
 * @param camera to be tested
 * @returns true if the camera is fully defined.
 */
function cameraDefinesBoundingBox(camera) {
    return (Array.isArray(camera === null || camera === void 0 ? void 0 : camera.zoom) &&
        !isEmptyBox3D(camera.zoom));
}
/**
 * Returns true if the camera can be defined from the parameters.
 * @param camera to be tested
 * @param boundingBox fallback bounding box, if the camera zoom is not a zoom value nor a bounding box.
 * @param size widget size in pixels.
 * @returns true if the camera can be defined from the parameters.
 */
function canCameraBeDefined(camera, boundingBox, bounds, size) {
    if (isCameraDefined(camera)) {
        return true;
    }
    return (size.height > 0 &&
        size.width > 0 &&
        (cameraDefinesBoundingBox(camera) ||
            !isEmptyBox3D(boundingBox) ||
            !isEmptyBox2D(typeof bounds === "function" ? bounds() : bounds)));
}
/**
 * Applies the zScale to the camera target.
 * This is needed, as the camera target is specified in world coordinates, while the Z scale
 * is applied to the transformation matrix of the object coordinates. The target must be applied
 * the same scale to be consistent with the display.
 * @param target camera target that must take into account the Z scale.
 * @param zScale Z scale.
 */
function applyZScale(target, zScale) {
    if ((target === null || target === void 0 ? void 0 : target[2]) != undefined) {
        target[2] = target[2] * zScale;
    }
}
/**
 * Returns a z-scaled target.
 * This is needed, as the camera target is specified in world coordinates, while the Z scale
 * is applied to the transformation matrix of the object coordinates. The target must be applied
 * the same scale to be consistent with the display.
 * @param target camera target that must take into account the Z scale.
 * @param zScale Z scale.
 */
function zScaledTarget(target, zScale) {
    if (!target) {
        return undefined;
    }
    if (target[2] == undefined) {
        return [target[0], target[1]];
    }
    return [target[0], target[1], target[2] * zScale];
}
/**
 * Applies the inverse of the zScale to the target point.
 * This is needed, as the camera target is specified in world coordinates, while the Z scale
 * is applied to the transformation matrix of the object coordinates. The target must be applied
 * the same scale to be consistent with the display.
 * @param target point that must be converted to user values (ie. without scale).
 * @param zScale Z scale.
 */
function applyInverseZScale(target, zScale) {
    if ((target === null || target === void 0 ? void 0 : target[2]) != undefined && zScale != 0) {
        target[2] = target[2] / zScale;
    }
}
/**
 * Returns an inverted z-scaled target.
 * This is needed, as the camera target is specified in world coordinates, while the Z scale
 * is applied to the transformation matrix of the object coordinates. The target must be applied
 * the same scale to be consistent with the display.
 * @param target camera scaled target.
 * @param zScale Z scale.
 * @param unscaledTarget last known unscaled target which can be taken as a fallback.
 */
function inversedZScaled(target, zScale, unscaledTarget) {
    if (!target) {
        return undefined;
    }
    if (target[2] == undefined) {
        return [target[0], target[1]];
    }
    if (zScale != 0) {
        return [target[0], target[1], target[2] / zScale];
    }
    if ((unscaledTarget === null || unscaledTarget === void 0 ? void 0 : unscaledTarget[2]) != undefined &&
        target[0] === (unscaledTarget === null || unscaledTarget === void 0 ? void 0 : unscaledTarget[0]) &&
        target[1] === (unscaledTarget === null || unscaledTarget === void 0 ? void 0 : unscaledTarget[1])) {
        return [
            target[0],
            target[1],
            target[2] ? target[2] : unscaledTarget[2],
        ];
    }
    return [target[0], target[1], target[2]];
}
/**
 * Returns the camera if it is fully specified (ie. the zoom is a valid number), otherwise computes
 * the zoom to visualize the complete camera boundingBox if set, the provided boundingBox otherwise.
 * @param camera input camera.
 * @param boundingBox fallback bounding box, if the camera zoom is not a zoom value nor a bounding box.
 * @param size widget size in pixels.
 */
function updateViewState(camera, boundingBox, size, is3D = true) {
    var _a, _b;
    if (isCameraDefined(camera)) {
        return camera;
    }
    // update the camera to see the whole boundingBox
    if (Array.isArray(camera.zoom) &&
        !isEmptyBox3D(camera.zoom)) {
        boundingBox = camera.zoom;
    }
    if (!is3D) {
        // in 2D, use flat boxes
        boundingBox[2] = 0;
        boundingBox[5] = 0;
    }
    if (!cameraHasZoom(camera)) {
        camera.zoom = computeCameraZoom(camera, boundingBox, size);
    }
    if (!cameraHasTarget(camera)) {
        camera.target = boxCenter(boundingBox);
    }
    camera.minZoom = (_a = camera.minZoom) !== null && _a !== void 0 ? _a : minZoom3D;
    camera.maxZoom = (_b = camera.maxZoom) !== null && _b !== void 0 ? _b : maxZoom3D;
    return camera;
}
/**
 *
 * @returns Computes the view state
 */
function computeViewState(viewPort, scaledCamera, boundingBox, bounds, viewportMargins, views, size) {
    // If the camera is defined, use it
    const isCameraPositionDefined = scaledCamera != undefined;
    const isBoundsDefined = bounds &&
        !isEmptyBox2D(typeof bounds == "function" ? bounds() : bounds);
    // defaults to avoid non invertible matrix issues while picking
    if (!boundingBox || isEmptyBox3D(boundingBox)) {
        boundingBox = [-1, -1, -1, 1, 1, 1];
    }
    if (show3D(viewPort)) {
        // If the camera is defined, use it
        if (isCameraPositionDefined) {
            return updateViewState(scaledCamera, boundingBox, size);
        }
        // deprecated in 3D, kept for backward compatibility
        if (isBoundsDefined) {
            const centerOfData = boxCenter(boundingBox);
            return getViewStateFromBounds(viewportMargins, bounds, centerOfData, views, viewPort, size);
        }
        const defaultCamera = {
            target: undefined, // force computation from the bounding box 3D
            zoom: NaN, // force computation from the bounding box 3D
            rotationX: 45, // look down z -axis at 45 degrees
            rotationOrbit: 0,
        };
        return updateViewState(defaultCamera, boundingBox, size);
    }
    else {
        const is3D = false;
        // If the camera is defined, use it
        if (isCameraPositionDefined) {
            return updateViewState(scaledCamera, boundingBox, size, is3D);
        }
        const centerOfData = boxCenter(boundingBox);
        // In 2D set camera target (centerOfData) to 0. Zoom scales world around target.
        centerOfData[2] = 0;
        // if bounds are defined, use them
        if (isBoundsDefined) {
            return getViewStateFromBounds(viewportMargins, bounds, centerOfData, views, viewPort, size);
        }
        return getViewStateFromBounds(viewportMargins, 
        // use the bounding box to extract the 2D bounds
        [boundingBox[0], boundingBox[1], boundingBox[3], boundingBox[4]], centerOfData, views, viewPort, size);
    }
}
/**
 * Builds the view states for the DeckGL views.
 * These view states are in user space (ie. not scaled by the zScale).
 * @param views requested views.
 * @param viewPortMargins margin between the viewports.
 * @param scaledCamera camera to apply to the views.
 * @param boundingBox data bounding box, used if no target nor bounding box is specified by the camera
 * @param zScale vertical scale.
 * @param bounds displayed 2D bounds in 2D view.
 * @param size deck component size.
 * @returns
 */
function buildViewStates(views, viewPortMargins, scaledCamera, boundingBox, bounds, size) {
    const isOk = areViewsValid(views, size);
    if (!views || !isOk) {
        return {};
    }
    // compute
    const [nY, nX] = views.layout;
    // compute for single view (code is more readable)
    const singleView = nX === 1 && nY === 1;
    if (singleView) {
        const viewState = computeViewState(views.viewports[0], scaledCamera, boundingBox, bounds, viewPortMargins, views, size);
        return { [views.viewports[0].id]: viewState };
    }
    // compute for matrix
    let result = {};
    for (let y = 1; y <= nY; y++) {
        for (let x = 1; x <= nX; x++) {
            const resultLength = Object.keys(result).length;
            if (resultLength >= views.viewports.length) {
                // stop when all the viewports are filled
                return result;
            }
            const currentViewport = views.viewports[resultLength];
            const currentViewState = computeViewState(currentViewport, scaledCamera, boundingBox, bounds, viewPortMargins, views, size);
            if (currentViewState) {
                result = Object.assign(Object.assign({}, result), { 
                    // Each viewport should have distinct state objects, so we clone to avoid any shared object references
                    [currentViewport.id]: cloneDeep(currentViewState) });
            }
        }
    }
    return result;
}
/**
 * Builds the scaled view states for the DeckGL views.
 * These view states are scaled by the zScale.
 * They shared all the fields except the scaled target with the provided view states.
 * @param viewStates view states in user space.
 * @param zScale vertical scale.
 * @returns
 */
function buildScaledViewStates(viewStates, zScale) {
    const result = cloneDeep(viewStates);
    for (const key in result) {
        const viewState = result[key];
        if (viewState) {
            applyZScale(viewState.target, zScale);
        }
    }
    return result;
}
function handleMouseEvent(type, infos, event) {
    var _a;
    const ev = {
        type: type,
        infos: infos,
    };
    if (ev.type === "click") {
        if (event.rightButton)
            ev.type = "contextmenu";
    }
    for (const info of infos) {
        if (info.coordinate) {
            ev.x = info.coordinate[0];
            ev.y = info.coordinate[1];
        }
        if (info.layer && info.layer.id === "wells-layer") {
            // info.object is Feature or WellLog;
            {
                // try to use Object info (see DeckGL getToolTip callback)
                const feat = info.object;
                const properties = feat === null || feat === void 0 ? void 0 : feat.properties;
                if (properties) {
                    ev.wellname = properties["name"];
                    ev.wellcolor = properties["color"];
                }
            }
            if (!ev.wellname)
                if (info.object) {
                    ev.wellname = (_a = info.object.header) === null || _a === void 0 ? void 0 : _a["well"]; // object is WellLog
                }
            // eslint-disable-next-line @typescript-eslint/no-explicit-any
            if (info.properties) {
                for (const property of info.properties) {
                    if (!ev.wellcolor)
                        ev.wellcolor = property.color;
                    let propname = property.name;
                    if (propname) {
                        const sep = propname.indexOf(" ");
                        if (sep >= 0) {
                            if (!ev.wellname) {
                                ev.wellname = propname.substring(sep + 1);
                            }
                            propname = propname.substring(0, sep);
                        }
                    }
                    const names_md = [
                        "DEPTH",
                        "DEPT",
                        "MD" /*Measured Depth*/,
                        "TDEP" /*"Tool DEPth"*/,
                        "MD_RKB" /*Rotary Relly Bushing*/,
                    ]; // aliases for MD
                    const names_tvd = [
                        "TVD" /*True Vertical Depth*/,
                        "TVDSS" /*SubSea*/,
                        "DVER" /*"VERtical Depth"*/,
                        "TVD_MSL" /*below Mean Sea Level*/,
                    ]; // aliases for MD
                    if (names_md.find((name) => name == propname))
                        ev.md = parseFloat(property.value);
                    else if (names_tvd.find((name) => name == propname))
                        ev.tvd = parseFloat(property.value);
                    if (ev.md !== undefined &&
                        ev.tvd !== undefined &&
                        ev.wellname !== undefined)
                        break;
                }
            }
            break;
        }
    }
    return ev;
}
//# sourceMappingURL=Map.js.map