import { JSONConfiguration, JSONConverter } from "@deck.gl/json";
import DeckGL from "@deck.gl/react";
import React, { useEffect, useState, useCallback, useRef } from "react";
import Settings from "./settings/Settings";
import JSON_CONVERTER_CONFIG from "../utils/configuration";
import { useSelector, useDispatch } from "react-redux";
import { setSpec } from "../redux/actions";
import InfoCard from "./InfoCard";
import DistanceScale from "./DistanceScale";
import StatusIndicator from "./StatusIndicator";
import { DeckGLView } from "./DeckGLView";
import { isEmpty } from "lodash";
import ColorLegend from "./ColorLegend";
import { applyPropsOnLayers, getLayersWithDefaultProps, } from "../layers/utils/layerTools";
import ViewFooter from "./ViewFooter";
import fitBounds from "../utils/fit-bounds";
import { validateColorTables, validateLayers, } from "../../../inputSchema/schemaValidationUtil";
import { getLayersByType } from "../layers/utils/layerTools";
// eslint-disable-next-line @typescript-eslint/no-var-requires
const colorTables = require("@emerson-eps/color-tables/dist/component/color-tables.json");
const Map = ({ id, resources, layers, bounds, zoom, views, coords, scale, coordinateUnit, toolbar, legend, colorTables, editedData, setEditedData, checkDatafileSchema, onMouseEvent, children, }) => {
    var _a;
    const deckRef = useRef(null);
    // set initial view state based on supplied bounds and zoom in viewState
    const [viewState, setViewState] = useState(getViewState(bounds, zoom));
    // react on zoom prop change
    useEffect(() => {
        var _a;
        const vs = getViewState(bounds, zoom, (_a = deckRef.current) === null || _a === void 0 ? void 0 : _a.deck);
        setViewState(Object.assign(Object.assign({}, viewState), { zoom: vs.zoom }));
    }, [zoom]);
    // react on bounds prop change
    useEffect(() => {
        var _a;
        const vs = getViewState(bounds, zoom, (_a = deckRef.current) === null || _a === void 0 ? void 0 : _a.deck);
        setViewState(Object.assign(Object.assign({}, viewState), { target: vs.target }));
    }, [bounds]);
    // calculate view state on deckgl context load (based on viewport size)
    const onLoad = useCallback(() => {
        var _a;
        setViewState(getViewState(bounds, zoom, (_a = deckRef.current) === null || _a === void 0 ? void 0 : _a.deck));
    }, []);
    // state for views prop of DeckGL component
    const [viewsProps, setViewsProps] = useState([]);
    useEffect(() => {
        setViewsProps(getViews(views));
    }, [views]);
    const [deckGLViews, setDeckGLViews] = useState([]);
    useEffect(() => {
        setDeckGLViews(jsonToObject(viewsProps));
    }, [viewsProps]);
    // update store if any of the layer prop is changed
    const dispatch = useDispatch();
    const st_layers = useSelector((st) => st.spec["layers"]);
    React.useEffect(() => {
        if (st_layers == undefined || layers == undefined)
            return;
        const updated_layers = applyPropsOnLayers(st_layers, layers);
        const layers_default = getLayersWithDefaultProps(updated_layers);
        const updated_spec = { layers: layers_default, views: views };
        dispatch(setSpec(updated_spec));
    }, [layers, dispatch]);
    const [deckGLLayers, setDeckGLLayers] = useState([]);
    useEffect(() => {
        const layers = st_layers;
        if (!layers || layers.length == 0)
            return;
        const enumerations = [];
        if (resources)
            enumerations.push({ resources: resources });
        if (editedData)
            enumerations.push({ editedData: editedData });
        else
            enumerations.push({ editedData: {} });
        setDeckGLLayers(jsonToObject(layers, enumerations));
    }, [st_layers, resources, editedData]);
    // eslint-disable-next-line @typescript-eslint/no-explicit-any
    const [hoverInfo, setHoverInfo] = useState([]);
    const onHover = useCallback((pickInfo, event) => {
        const infos = getPickingInfos(pickInfo, event);
        setHoverInfo(infos); //  for InfoCard pickInfos
        callOnMouseEvent("hover", infos, event);
    }, [coords, onMouseEvent]);
    const onClick = useCallback((pickInfo, event) => {
        const infos = getPickingInfos(pickInfo, event);
        callOnMouseEvent("click", infos, event);
    }, [coords, onMouseEvent]);
    const getPickingInfos = (pickInfo, 
    // eslint-disable-next-line @typescript-eslint/no-explicit-any
    event) => {
        if ((coords === null || coords === void 0 ? void 0 : coords.multiPicking) && pickInfo.layer) {
            return pickInfo.layer.context.deck.pickMultipleObjects({
                x: event.offsetCenter.x,
                y: event.offsetCenter.y,
                radius: 1,
                depth: coords.pickDepth,
            });
        }
        return [pickInfo];
    };
    /**
     * call onMouseEvent callback
     */
    const callOnMouseEvent = (type, infos, 
    // eslint-disable-next-line @typescript-eslint/no-explicit-any
    event) => {
        var _a;
        if (onMouseEvent) {
            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];
                }
                //const layer_name = (info.layer?.props as ExtendedLayerProps<FeatureCollection>)?.name;
                if (info.layer && info.layer.id === "wells-layer") {
                    // eslint-disable-next-line @typescript-eslint/no-explicit-any
                    (_a = info.properties) === null || _a === void 0 ? void 0 : _a.forEach((property) => {
                        let propname = property.name;
                        if (propname) {
                            const sep = propname.indexOf(" ");
                            if (sep >= 0) {
                                // it is a simple hack to get well name previously added to the end of property name
                                ev.wellname = propname.substring(sep + 1);
                                propname = propname.substring(0, sep);
                            }
                        }
                        if (propname === "MD")
                            ev.md = parseFloat(property.value);
                        else if (propname === "TVD")
                            ev.tvd = parseFloat(property.value);
                    });
                    break;
                }
            }
            onMouseEvent(ev);
        }
    };
    const [isLoaded, setIsLoaded] = useState(false);
    const onAfterRender = useCallback(() => {
        if (deckGLLayers) {
            const state = deckGLLayers.every((layer) => layer.isLoaded);
            setIsLoaded(state);
        }
    }, [deckGLLayers]);
    // validate layers data
    const [errorText, setErrorText] = useState();
    useEffect(() => {
        var _a;
        const layers = (_a = deckRef.current) === null || _a === void 0 ? void 0 : _a.deck.props.layers;
        // this ensures to validate the schemas only once
        if (checkDatafileSchema && layers && isLoaded) {
            try {
                validateLayers(layers);
                colorTables && validateColorTables(colorTables);
            }
            catch (e) {
                setErrorText(String(e));
            }
        }
        else
            setErrorText(undefined);
    }, [checkDatafileSchema, (_a = deckRef.current) === null || _a === void 0 ? void 0 : _a.deck.props.layers, isLoaded]);
    const layerFilter = useCallback((args) => {
        // display all the layers if views are not specified correctly
        if (!views || !views.viewports || !views.layout)
            return true;
        const cur_view = views.viewports.find(({ id }) => args.viewport.id &&
            new RegExp("^" + id).test(args.viewport.id));
        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) => args.layer.id.match(new RegExp("\\b" + layer_id + "\\b")));
        }
        else {
            return true;
        }
    }, [views]);
    if (!deckGLViews || isEmpty(deckGLViews) || isEmpty(deckGLLayers))
        return null;
    return (React.createElement("div", null,
        React.createElement(DeckGL, { id: id, viewState: viewState, views: deckGLViews, layerFilter: layerFilter, layers: deckGLLayers, userData: {
                setEditedData: (updated_prop) => {
                    setEditedData === null || setEditedData === void 0 ? void 0 : setEditedData(updated_prop);
                },
                colorTables: colorTables,
            }, getCursor: ({ isDragging }) => isDragging ? "grabbing" : "default", 
            // @ts-expect-error: Fix type in WellsLayer
            getTooltip: (info) => {
                var _a, _b, _c, _d, _e;
                if ((_a = info) === null || _a === void 0 ? void 0 : _a.logName) {
                    return (_b = info) === null || _b === void 0 ? void 0 : _b.logName;
                }
                else if (((_c = info.layer) === null || _c === void 0 ? void 0 : _c.id) === "drawing-layer") {
                    return (_d = info.measurement) === null || _d === void 0 ? void 0 : _d.toFixed(2);
                }
                else {
                    const feat = info.object;
                    return (_e = feat === null || feat === void 0 ? void 0 : feat.properties) === null || _e === void 0 ? void 0 : _e["name"];
                }
            }, ref: deckRef, onViewStateChange: (viewport) => setViewState(viewport.viewState), onHover: onHover, onClick: onClick, onLoad: onLoad, onAfterRender: onAfterRender },
            children,
            (views === null || views === void 0 ? void 0 : views.viewports) &&
                views.viewports.map((view, index) => {
                    var _a, _b, _c, _d;
                    return (React.createElement(DeckGLView, { key: `${view.id}_${view.show3D ? "3D" : "2D"}`, id: `${view.id}_${view.show3D ? "3D" : "2D"}` },
                        colorTables && (legend === null || legend === void 0 ? void 0 : legend.visible) && (React.createElement(ColorLegend, Object.assign({}, legend, { layers: [
                                (_b = getLayersByType((_a = deckRef.current) === null || _a === void 0 ? void 0 : _a.deck.props.layers, "WellsLayer")) === null || _b === void 0 ? void 0 : _b[0],
                                (_d = getLayersByType((_c = deckRef.current) === null || _c === void 0 ? void 0 : _c.deck.props.layers, "ColormapLayer")) === null || _d === void 0 ? void 0 : _d[0],
                            ], colorTables: colorTables }))),
                        (toolbar === null || toolbar === void 0 ? void 0 : toolbar.visible) && (React.createElement(Settings, { viewportId: view.id, layerIds: view.layerIds })),
                        views.showLabel && (React.createElement(ViewFooter, null, `${view.name
                            ? view.name
                            : `View_${index + 1}`} `))));
                })),
        (scale === null || scale === void 0 ? void 0 : scale.visible) ? (React.createElement(DistanceScale, Object.assign({}, scale, { zoom: viewState === null || viewState === void 0 ? void 0 : viewState.zoom, scaleUnit: coordinateUnit }))) : null,
        React.createElement(StatusIndicator, { layers: deckGLLayers, isLoaded: isLoaded }),
        (coords === null || coords === void 0 ? void 0 : coords.visible) ? React.createElement(InfoCard, { pickInfos: hoverInfo }) : null,
        errorText && (React.createElement("pre", { style: {
                flex: "0, 0",
                color: "rgb(255, 64, 64)",
                backgroundColor: "rgb(255, 255, 192)",
            } }, errorText))));
};
Map.defaultProps = {
    coords: {
        visible: true,
        multiPicking: true,
        pickDepth: 10,
    },
    scale: {
        visible: true,
        incrementValue: 100,
        widthPerUnit: 100,
        cssStyle: { top: 10, left: 10 },
    },
    toolbar: {
        visible: false,
    },
    legend: {
        visible: true,
        cssStyle: { top: 5, right: 10 },
        horizontal: false,
    },
    coordinateUnit: "m",
    views: {
        layout: [1, 1],
        showLabel: false,
        viewports: [{ id: "main-view", show3D: false, layerIds: [] }],
    },
    colorTables: colorTables,
    checkDatafileSchema: false,
};
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.
function jsonToObject(data, 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),
            });
        }
    });
    const jsonConverter = new JSONConverter({ configuration });
    // remove empty data/layer object
    const filtered_data = data.filter((value) => Object.keys(value).length !== 0);
    return jsonConverter.convert(filtered_data);
}
// return viewstate with computed bounds to fit the data in viewport
function getViewState(bounds, zoom, deck) {
    let width = bounds[2] - bounds[0]; // right - left
    let height = bounds[3] - bounds[1]; // top - bottom
    if (deck) {
        width = deck.width;
        height = deck.height;
    }
    const padding = 20;
    const fitted_bound = fitBounds({ width, height, bounds, padding });
    const view_state = {
        target: [fitted_bound.x, fitted_bound.y, 0],
        zoom: zoom !== null && zoom !== void 0 ? zoom : fitted_bound.zoom,
        rotationX: 90,
        rotationOrbit: 0,
    };
    return view_state;
}
// construct views object for DeckGL component
function getViews(views) {
    const deckgl_views = [];
    // if props for multiple viewport are not proper, return 2d view
    const far = 9999.9;
    const near = 0.0001;
    if (!views || !views.viewports || !views.layout) {
        deckgl_views.push({
            "@@type": "OrthographicView",
            id: "main",
            controller: { doubleClickZoom: false },
            x: "0%",
            y: "0%",
            width: "100%",
            height: "100%",
            flipY: false,
            far,
            near,
        });
    }
    else {
        let yPos = 0;
        const [nY, nX] = views.layout;
        for (let y = 1; y <= nY; y++) {
            let xPos = 0;
            for (let x = 1; x <= nX; x++) {
                if (views.viewports == undefined ||
                    deckgl_views.length >= views.viewports.length)
                    return deckgl_views;
                const cur_viewport = views.viewports[deckgl_views.length];
                const view_type = cur_viewport.show3D
                    ? "OrbitView"
                    : "OrthographicView";
                const id_suffix = cur_viewport.show3D ? "_3D" : "_2D";
                const view_id = cur_viewport.id + id_suffix;
                deckgl_views.push({
                    "@@type": view_type,
                    id: view_id,
                    controller: {
                        doubleClickZoom: false,
                    },
                    x: xPos + "%",
                    y: yPos + "%",
                    // Using 99.5% of viewport to avoid flickering of deckgl canvas
                    width: 99.5 / nX + "%",
                    height: 99.5 / nY + "%",
                    flipY: false,
                    far,
                    near,
                });
                xPos = xPos + 99.5 / nX;
            }
            yPos = yPos + 99.5 / nY;
        }
    }
    return deckgl_views;
}
//# sourceMappingURL=Map.js.map