"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    var desc = Object.getOwnPropertyDescriptor(m, k);
    if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
      desc = { enumerable: true, get: function() { return m[k]; } };
    }
    Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
    Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
    o["default"] = v;
});
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
    var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
    if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
    else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
    return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __importStar = (this && this.__importStar) || (function () {
    var ownKeys = function(o) {
        ownKeys = Object.getOwnPropertyNames || function (o) {
            var ar = [];
            for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
            return ar;
        };
        return ownKeys(o);
    };
    return function (mod) {
        if (mod && mod.__esModule) return mod;
        var result = {};
        if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
        __setModuleDefault(result, mod);
        return result;
    };
})();
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
    function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
    return new (P || (P = Promise))(function (resolve, reject) {
        function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
        function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
        function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
        step((generator = generator.apply(thisArg, _arguments || [])).next());
    });
};
var __rest = (this && this.__rest) || function (s, e) {
    var t = {};
    for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
        t[p] = s[p];
    if (s != null && typeof Object.getOwnPropertySymbols === "function")
        for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
            if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
                t[p[i]] = s[p[i]];
        }
    return t;
};
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.getFeatureStyleWithHighlight = exports.getFeatureStyleWithLowerOpacity = exports.Map = exports.MapComponent = void 0;
exports.parseGeometries = parseGeometries;
const React = __importStar(require("react"));
const PropTypes = __importStar(require("prop-types"));
const react_dom_1 = require("react-dom");
const immutability_helper_1 = __importDefault(require("immutability-helper"));
const equals = require("deep-equal");
const deepmerge_1 = __importDefault(require("deepmerge"));
const laji_map_1 = __importDefault(require("@luomus/laji-map"));
const utils_1 = require("@luomus/laji-map/lib/utils");
const globals_1 = require("@luomus/laji-map/lib/globals");
const components_1 = require("../components");
const utils_2 = require("../../utils");
const Context_1 = __importDefault(require("../../Context"));
const ReactContext_1 = __importDefault(require("../../ReactContext"));
const NestField_1 = require("./NestField");
const ArrayFieldTemplate_1 = require("../templates/ArrayFieldTemplate");
const ArrayField_1 = require("./ArrayField");
const utils_3 = require("@rjsf/utils");
const utils_4 = require("@luomus/laji-map/lib/utils");
function parseGeometries(geometry) {
    return ((geometry && geometry.type === "GeometryCollection") ? geometry.geometries : [geometry])
        .filter(geometry => geometry)
        .reduce((geometries, _geometry) => {
        if (geometry.type === "GeometryCollection") {
            geometries = [...geometries, ...parseGeometries(_geometry)];
        }
        else if ("type" in geometry) {
            geometries.push(geometry);
        }
        return geometries;
    }, []);
}
class MapArrayField extends React.Component {
    render() {
        const { geometryMapper = "default" } = (0, utils_2.getUiOptions)(this.props.uiSchema);
        switch (geometryMapper) {
            case "default":
                return React.createElement(DefaultMapArrayField, Object.assign({}, this.props));
            case "units":
                return React.createElement(UnitsMapArrayField, Object.assign({}, this.props));
            case "lineTransect":
                return React.createElement(LineTransectMapArrayField, Object.assign({}, this.props));
            case "lolife":
                return React.createElement(LolifeMapArrayField, Object.assign({}, this.props));
        }
    }
}
MapArrayField.propTypes = {
    uiSchema: PropTypes.shape({
        "ui:options": PropTypes.shape({
            geometryField: PropTypes.string,
            // Strategy for getting geometry data.
            geometryMapper: PropTypes.oneOf(["default", "units", "lineTransect", "lolife", "lolifeNamedPlace"]),
            topOffset: PropTypes.number,
            bottomOffset: PropTypes.number,
            popupFields: PropTypes.arrayOf(PropTypes.object),
            computeAreaField: PropTypes.string,
            areaInHectares: PropTypes.bool,
            mapSizes: PropTypes.shape({
                lg: PropTypes.number,
                md: PropTypes.number,
                sm: PropTypes.number,
                xs: PropTypes.number
            }),
            data: PropTypes.arrayOf(PropTypes.shape({
                geometryField: PropTypes.string.isRequired
            })),
            help: PropTypes.string
        }).isRequired,
    }),
    schema: PropTypes.shape({
        type: PropTypes.oneOf(["array"])
    }).isRequired,
    formData: PropTypes.oneOfType([PropTypes.array, PropTypes.object])
};
exports.default = MapArrayField;
let DefaultMapArrayField = class DefaultMapArrayField extends React.Component {
    constructor(props) {
        super(props);
        this.onMapChangeCreateGathering = this.onMapChangeCreateGathering.bind(this);
        this.onChange = this.onChange.bind(this);
        this.getGeometry = this.getGeometry.bind(this);
    }
    getOptions(options) {
        const { formData } = this.props;
        const geometries = this.getData();
        const emptyMode = !formData || !formData.length;
        const draw = (options.draw === false || ((0, utils_2.isNullOrUndefined)(this.state.activeIdx) && !emptyMode)) ? false : Object.assign({ featureCollection: {
                type: "FeatureCollection",
                features: (geometries || []).map(geometry => {
                    return { type: "Feature", properties: {}, geometry };
                })
            }, getDraftStyle: this.getDraftStyle, onChange: emptyMode ? this.onMapChangeCreateGathering : this.onChange }, (options.draw && options.draw.constructor === Object && options.draw !== null ? options.draw : {}));
        const controls = (emptyMode || this.state.activeIdx !== undefined)
            ? { drawCopy: true }
            : { draw: false };
        const data = geometries && geometries.length || !options.placeholderGeometry
            ? []
            : [{
                    geoData: options.placeholderGeometry,
                    getFeatureStyle: this._getPlaceholderStyle
                }];
        const extraData = (options.data || []).map((dataItem) => ({ geoData: dataItem.geometryField ? this.getGeometry(formData, dataItem.geometryField) : dataItem }));
        return { draw, controls, emptyMode, data: [...data, ...extraData] };
    }
    onMapChangeCreateGathering(events) {
        const { geometryField } = (0, utils_2.getUiOptions)(this.props.uiSchema);
        if (!geometryField) {
            return;
        }
        let formData = (0, utils_2.getDefaultFormState)(this.props.schema.items);
        const geometries = events.filter(e => e.type === "create").map(e => e.feature.geometry);
        let splittedPath = geometryField.split("/").filter(s => !(0, utils_2.isEmptyString)(s));
        let _cumulatedPointer = "";
        let _schema = this.props.schema.items;
        formData = splittedPath.reduce((_formData, split, i) => {
            _cumulatedPointer += `/${split}`;
            _schema = _schema.properties
                ? _schema.properties[split]
                : _schema.items;
            return (0, immutability_helper_1.default)(_formData, (0, utils_2.getUpdateObjectFromJSONPointer)(_cumulatedPointer, { $set: i === splittedPath.length - 1
                    ? {
                        type: "GeometryCollection",
                        geometries
                    }
                    : (0, utils_2.getDefaultFormState)(_schema)
            }));
        }, formData);
        this.props.onChange((0, ArrayField_1.onArrayFieldChange)([formData], this.props));
        this.setState({ activeIdx: 0 });
    }
    getGeometry(formData, _geometryField) {
        const { uiSchema } = this.props;
        const { geometryField } = (0, utils_2.getUiOptions)(uiSchema);
        const { activeIdx } = this.state;
        if (!formData) {
            return;
        }
        const item = formData[activeIdx];
        if (activeIdx === undefined || !item) {
            return;
        }
        const geometryItem = (0, utils_2.parseJSONPointer)(formData[activeIdx], _geometryField || geometryField);
        return geometryItem;
    }
    getGeometries() {
        return this.getData();
    }
    getData() {
        const geometryItem = this.getGeometry(this.props.formData);
        if (geometryItem && geometryItem.type) {
            return parseGeometries(geometryItem);
        }
        return [];
    }
    onChange(events) {
        let formData = this.props.formData ||
            [(0, utils_2.getDefaultFormState)(this.props.schema.items)];
        let addOrDelete = false;
        events.forEach(e => {
            switch (e.type) {
                case "create":
                    addOrDelete = true;
                    formData = this.onAdd(e, formData);
                    break;
                case "delete":
                    addOrDelete = true;
                    formData = this.onRemove(e, formData);
                    break;
                case "edit":
                    formData = this.onEdited(e, formData);
                    break;
                case "insert":
                    addOrDelete = true;
                    formData = this.onInsert(e, formData);
                    break;
            }
        });
        this.props.onChange(addOrDelete ? (0, ArrayField_1.onArrayFieldChange)(formData, this.props) : formData);
    }
    onAdd({ feature: { geometry } }, formData) {
        const { geometryField } = (0, utils_2.getUiOptions)(this.props.uiSchema);
        return (0, utils_2.updateSafelyWithJSONPointer)(formData, { type: "GeometryCollection", geometries: [...parseGeometries(this.getGeometry(formData)), geometry] }, `/${this.state.activeIdx}/${geometryField}`);
    }
    onRemove({ idxs }, formData) {
        const { geometryField } = (0, utils_2.getUiOptions)(this.props.uiSchema);
        let splices = [];
        idxs.sort().reverse().forEach((idx) => {
            splices.push([idx, 1]);
        });
        const geometry = this.getGeometry(formData);
        return (0, immutability_helper_1.default)(formData, { [this.state.activeIdx]: (0, utils_2.getUpdateObjectFromJSONPointer)(geometryField, geometry && geometry.type === "GeometryCollection" ?
                { geometries: { $splice: splices } } : { $set: undefined }) });
    }
    onEdited({ features }, formData) {
        const { geometryField } = (0, utils_2.getUiOptions)(this.props.uiSchema);
        const geometry = this.getGeometry(formData);
        return (0, immutability_helper_1.default)(formData, { [this.state.activeIdx]: (0, utils_2.getUpdateObjectFromJSONPointer)(geometryField, geometry.type === "GeometryCollection" ? {
                geometries: Object.keys(features).reduce((obj, idx) => {
                    obj[idx] = { $set: features[idx].geometry };
                    return obj;
                }, {})
            } : { $set: features[0].geometry }) });
    }
    onInsert({ idx, feature }, formData) {
        const { geometryField } = (0, utils_2.getUiOptions)(this.props.uiSchema);
        return (0, immutability_helper_1.default)(formData, { [this.state.activeIdx]: (0, utils_2.getUpdateObjectFromJSONPointer)(geometryField, {
                geometries: { $splice: [[idx, 0, feature.geometry]] }
            }) });
    }
    afterActiveChange(idx) {
        if (idx === undefined)
            return;
        this.map.zoomToData();
    }
};
DefaultMapArrayField = __decorate([
    _MapArrayField
], DefaultMapArrayField);
let UnitsMapArrayField = class UnitsMapArrayField extends React.Component {
    constructor(props) {
        super(props);
        this.field = "units";
        this.isGeometryCollection = (idx) => {
            return this.props.formData[this.state.activeIdx].units[idx].unitGathering.geometry.type === "GeometryCollection";
        };
        this.getOptions = (options) => {
            const { formData } = this.props;
            const { gatherings = [], units = [] } = this.getData();
            const emptyMode = !formData || !formData.length;
            const draw = (options.draw === false || ((0, utils_2.isNullOrUndefined)(this.state.activeIdx) && !emptyMode)) ? false : Object.assign({ featureCollection: {
                    type: "FeatureCollection",
                    features: gatherings.map(geometry => {
                        return { type: "Feature", properties: {}, geometry };
                    })
                }, getDraftStyle: this.getDraftStyle, onChange: emptyMode ? this.onMapChangeCreateGathering : this.onChange }, (options.draw && options.draw.constructor === Object && options.draw !== null ? options.draw : {}));
            // Separate non geometry collections and geometry collections with single geometry to
            // unitGeometries, and put the rest to unitGeometryCollections. This is for optimization.
            let unitGeometryCollections = {}, unitGeometries = {};
            units.forEach((geometry, idx) => {
                const target = (geometry.type !== "GeometryCollection" || geometry.geometries.length === 1)
                    ? unitGeometries
                    : unitGeometryCollections;
                target[idx] = geometry.type === "GeometryCollection" && geometry.geometries.length === 1
                    ? geometry.geometries[0]
                    : geometry;
            });
            this.unitIdxToGeometryIdx = {};
            this.geometryIdxUnitIdx = {};
            this.unitIdxToGeometryCollectionIdx = {};
            this.geometryCollectionIdxUnitIdx = {};
            const reducer = ([uToG, gToU], idx, _idx) => {
                uToG[idx] = _idx;
                gToU[_idx] = idx;
                return [uToG, gToU];
            };
            Object.keys(unitGeometries).reduce(reducer, [this.unitIdxToGeometryIdx, this.geometryIdxUnitIdx]);
            Object.keys(unitGeometryCollections).reduce(reducer, [this.unitIdxToGeometryCollectionIdx, this.geometryCollectionIdxUnitIdx]);
            if (!this.unitChangeHandlers) {
                this.unitChangeHandlers = { onChange: {}, mouseOver: {}, mouseOut: {} };
            }
            const getCommonOptions = unitIdx => {
                // Store so change detection doesn't think it's a new function.
                const unitChangeHandler = this.unitChangeHandlers.onChange[unitIdx] || this.onUnitChange(unitIdx ? +unitIdx : undefined);
                this.unitChangeHandlers.onChange[unitIdx] = unitChangeHandler;
                const mouseOver = this.unitChangeHandlers.mouseOver[unitIdx] || this.onMouseOver(unitIdx ? +unitIdx : undefined);
                this.unitChangeHandlers.mouseOver[unitIdx] = mouseOver;
                const mouseOut = this.unitChangeHandlers.mouseOut[unitIdx] || this.onMouseOut(unitIdx ? +unitIdx : undefined);
                this.unitChangeHandlers.mouseOut[unitIdx] = mouseOut;
                return {
                    getPopup: this.getPopup,
                    getFeatureStyle: this.getUnitFeatureStyle,
                    getDraftStyle: this.getDraftStyle,
                    editable: true,
                    onChange: unitChangeHandler,
                    on: {
                        mouseover: mouseOver,
                        mouseout: mouseOut
                    }
                };
            };
            const data = [
                Object.assign({ featureCollection: {
                        type: "FeatureCollection",
                        features: Object.keys(unitGeometries).reduce((units, idx) => {
                            idx = +idx;
                            const geometry = unitGeometries[idx];
                            if (geometry)
                                units.push({ type: "Feature", properties: { idx }, geometry });
                            return units;
                        }, [])
                    } }, getCommonOptions()),
                ...Object.keys(unitGeometryCollections).map(idx => {
                    idx = +idx;
                    const geometryCollection = unitGeometryCollections[idx];
                    return Object.assign({ featureCollection: {
                            type: "FeatureCollection",
                            features: geometryCollection.geometries.map(geometry => ({
                                type: "Feature", properties: { idx }, geometry
                            }))
                        } }, getCommonOptions(this.geometryCollectionIdxUnitIdx[idx]));
                })
            ];
            this.unitFeatures = data[0].featureCollection.features;
            const controls = (emptyMode || !(0, utils_2.isNullOrUndefined)(this.state.activeIdx)) ?
                {} : { draw: false };
            return { draw, data, controls, emptyMode };
        };
        this.getUnitFeatureStyle = () => {
            let color = "#55AEFA";
            if (this._highlightedUnit !== undefined) {
                color = (0, utils_1.combineColors)(color, "#ffffff", 30);
            }
            return { color: color, fillColor: color, weight: 4 };
        };
        this.startHighlight = ({ idx }) => {
            const color = (0, utils_1.combineColors)(this.getUnitFeatureStyle().color, "#ffffff", 30);
            if (idx in this.unitIdxToGeometryCollectionIdx) {
                const _idx = this.unitIdxToGeometryCollectionIdx[idx];
                this.map.data[_idx + 1].group.getLayers().forEach(layer => {
                    this.map.setLayerStyle(layer, { color });
                });
            }
            else {
                const _idx = this.unitIdxToGeometryIdx[idx];
                const layer = this.map.getLayerByIdxTuple([0, _idx]);
                layer && this.map.setLayerStyle(layer, { color });
            }
        };
        this.endHighlight = ({ idx }) => {
            const color = this.getUnitFeatureStyle().color;
            if (idx in this.unitIdxToGeometryCollectionIdx) {
                const _idx = this.unitIdxToGeometryCollectionIdx[idx];
                this.map.data[_idx + 1].group.getLayers().forEach(layer => {
                    this.map.setLayerStyle(layer, { color });
                });
            }
            else {
                const _idx = this.unitIdxToGeometryIdx[idx];
                const layer = this.map.getLayerByIdxTuple([0, _idx]);
                layer && this.map.setLayerStyle(layer, { color });
            }
        };
        this.onMouseOver = (unitIdx) => (e, { feature }) => {
            const idx = unitIdx !== undefined ? unitIdx : feature.properties.idx;
            if (idx === undefined) {
                return;
            }
            this._highlightedUnit = idx;
            this.startHighlight(idx);
            const id = `${this.props.idSchema.$id}_${this.state.activeIdx}_units_${idx}`;
            this.highlightedElem = this.props.formContext.utils.getSchemaElementById(id);
            if (this.highlightedElem) {
                this.highlightedElem.className += " map-highlight";
            }
        };
        this.onMouseOut = (unitIdx) => (e, { feature }) => {
            const idx = unitIdx !== undefined ? unitIdx : feature.properties.idx;
            if (idx === undefined) {
                return;
            }
            this._highlightedUnit = undefined;
            this.endHighlight(idx);
            if (this.highlightedElem) {
                this.highlightedElem.className = this.highlightedElem.className.replace(" map-highlight", "");
            }
        };
        this.onUnitChange = (unitIdx) => (events) => {
            events.forEach(e => {
                switch (e.type) {
                    case "delete":
                        this.onUnitRemove(e, unitIdx);
                        break;
                    case "edit":
                        this.onUnitEdited(e, unitIdx);
                        break;
                }
            });
        };
        this.onUnitRemove = (e, unitIdx) => {
            if (unitIdx === undefined) { // Isn't a geometry collection (unitIdx stored in feature properties)
                const idxs = e.idxs.map(idx => this.unitFeatures[idx].properties.idx);
                const { formData } = this.props;
                const unitIdxs = idxs;
                let updateObject = { [this.state.activeIdx]: {
                        units: unitIdxs.reduce((obj, idx) => {
                            obj[idx] = {
                                unitGathering: {
                                    geometry: {
                                        $set: (0, utils_2.getDefaultFormState)(this.props.schema.items.properties.units.items.properties.unitGathering.properties.geometry)
                                    }
                                }
                            };
                            return obj;
                        }, {})
                    } };
                this.props.onChange((0, immutability_helper_1.default)(formData, updateObject));
            }
            else { // Is a geometry collection.
                const { idxs } = e;
                this.props.onChange((0, immutability_helper_1.default)(this.props.formData, {
                    [this.state.activeIdx]: {
                        units: {
                            [unitIdx]: {
                                unitGathering: {
                                    geometry: {
                                        geometries: {
                                            $splice: idxs.sort().reverse().map(idx => [idx, 1])
                                        }
                                    }
                                }
                            }
                        }
                    }
                }));
            }
        };
        this.onUnitEdited = ({ features }, unitIdx) => {
            if (unitIdx === undefined) { // Isn't a geometry collection (unitIdx stored in feature properties)
                const unitEditGeometries = Object.keys(features).reduce((unitEditGeometries, idx) => {
                    unitEditGeometries[features[idx].properties.idx] = features[idx].geometry;
                    return unitEditGeometries;
                }, {});
                const updateObject = {
                    [this.state.activeIdx]: {
                        units: Object.keys(unitEditGeometries).reduce((o, i) => {
                            o[i] = { unitGathering: { geometry: { $set: unitEditGeometries[i] } } };
                            return o;
                        }, {})
                    }
                };
                this.props.onChange((0, immutability_helper_1.default)(this.props.formData, updateObject));
            }
            else { // Is a geometry collection.
                this.props.onChange((0, immutability_helper_1.default)(this.props.formData, {
                    [this.state.activeIdx]: {
                        units: {
                            [unitIdx]: {
                                unitGathering: {
                                    geometry: { $set: Object.keys(features).reduce((geometryCollection, idx) => {
                                            const feature = features[idx];
                                            geometryCollection.geometries[idx] = feature.geometry;
                                            return geometryCollection;
                                        }, this.props.formData[this.state.activeIdx].units[unitIdx].unitGathering.geometry)
                                    }
                                }
                            }
                        }
                    }
                }));
            }
        };
        this.onMapChangeCreateGathering = DefaultMapArrayField.prototype.onMapChangeCreateGathering.bind(this);
        this.onChange = DefaultMapArrayField.prototype.onChange.bind(this);
        this.onAdd = DefaultMapArrayField.prototype.onAdd.bind(this);
        this.onRemove = DefaultMapArrayField.prototype.onRemove.bind(this);
        this.onEdited = DefaultMapArrayField.prototype.onEdited.bind(this);
        this.onInsert = DefaultMapArrayField.prototype.onInsert.bind(this);
        this.afterActiveChange = DefaultMapArrayField.prototype.afterActiveChange.bind(this);
        this.getGeometry = DefaultMapArrayField.prototype.getGeometry.bind(this);
    }
    componentDidMount() {
        this.props.formContext.services.customEvents.add(this.props.idSchema.$id, "startHighlight", this.startHighlight);
        this.props.formContext.services.customEvents.add(this.props.idSchema.$id, "endHighlight", this.endHighlight);
    }
    componentWillUnmount() {
        this.props.formContext.services.customEvents.remove(this.props.idSchema.$id, "startHighlight", this.startHighlight);
        this.props.formContext.services.customEvents.remove(this.props.idSchema.$id, "endHighlight", this.endHighlight);
    }
    getData() {
        const { formData } = this.props;
        const idx = this.state.activeIdx;
        if (!formData)
            return {};
        const item = formData[idx];
        const gatherings = DefaultMapArrayField.prototype.getData.call(this);
        const units = ((item && item.units) ? item.units : []).reduce((units, unit, idx) => {
            if (unit.unitGathering) {
                const { unitGathering: { geometry } } = unit;
                if (geometry && (0, utils_2.hasData)(geometry)) {
                    units[idx] = geometry;
                }
            }
            return units;
        }, []);
        return { gatherings, units };
    }
    getGeometries() {
        const { gatherings, units } = this.getData();
        return [
            ...parseGeometries(gatherings),
            ...parseGeometries(units)
        ];
    }
    parsePopupPointer(col, options) {
        return col.replace("[idx]", options.feature.properties.idx);
    }
};
UnitsMapArrayField = __decorate([
    _MapArrayField
], UnitsMapArrayField);
let LineTransectMapArrayField = class LineTransectMapArrayField extends React.Component {
    constructor(props) {
        super(props);
        this.onLineCreate = ([event]) => {
            this.props.onChange((0, ArrayField_1.onArrayFieldChange)((0, immutability_helper_1.default)(this.props.formData, { 0: { geometry: { $set: event.feature.geometry
                    } } }), this.props));
        };
        this.onChange = (events) => {
            const { geometryField } = (0, utils_2.getUiOptions)(this.props.uiSchema);
            let state = {};
            let { formData } = this.props;
            let formDataChanged = false;
            let addOrDelete = false;
            events.forEach(e => {
                switch (e.type) {
                    case "insert": {
                        formDataChanged = true;
                        addOrDelete = true;
                        const newItem = (0, utils_2.getDefaultFormState)(this.props.schema.items);
                        newItem[geometryField] = e.geometry;
                        formData = (0, immutability_helper_1.default)(formData, {
                            $splice: [[e.idx, 0, newItem]]
                        });
                        break;
                    }
                    case "edit": {
                        formDataChanged = true;
                        formData = (0, immutability_helper_1.default)(formData, {
                            [e.idx]: {
                                [geometryField]: { $set: e.geometry }
                            }
                        });
                        break;
                    }
                    case "delete": {
                        formDataChanged = true;
                        addOrDelete = true;
                        formData = (0, immutability_helper_1.default)(formData, { $splice: [[e.idx, 1]] });
                        break;
                    }
                    case "merge": {
                        formDataChanged = true;
                        addOrDelete = true;
                        const [first, second] = e.idxs;
                        formData = (0, immutability_helper_1.default)(formData, { [first]: { units: { $set: [
                                        ...(formData[first].units || []),
                                        ...(formData[second].units || [])
                                    ] } } });
                        formData = (0, immutability_helper_1.default)(formData, {
                            [first]: {
                                [geometryField]: { $set: e.geometry }
                            }
                        });
                        formData = (0, immutability_helper_1.default)(formData, { $splice: [[second, 1]] });
                        break;
                    }
                    case "move": {
                        formDataChanged = true;
                        const { idx, target } = e;
                        let splices = [
                            [idx, 1],
                            [target, 0, formData[idx]],
                        ];
                        // Splices must be executed in reverse order to keep idxs correct.
                        if (target > idx)
                            splices = splices.reverse();
                        formData = (0, immutability_helper_1.default)(formData, { $splice: splices });
                        break;
                    }
                    case "active": {
                        state.activeIdx = e.idx;
                    }
                }
            });
            const afterState = () => {
                if (formDataChanged) {
                    formData = syncLineTransectStartEnd(formData);
                    this.props.onChange(addOrDelete ? (0, ArrayField_1.onArrayFieldChange)(formData, this.props) : formData);
                }
                // if ("activeIdx" in state) {
                // 	this.afterActiveChange(state.activeIdx);
                // }
            };
            Object.keys(state).length ? this.setState(state, afterState()) : afterState();
        };
        this.getFeatureStyle = ({ lineIdx, style, type }) => {
            if (this.props.errorSchema[lineIdx]) {
                return (type === L.CircleMarker)
                    ? style
                    : Object.assign(Object.assign({}, style), { fillColor: "#f33" });
            }
            const { gatheringFact = {} } = this.props.formData[lineIdx] || {};
            const { lineTransectSegmentCounted } = gatheringFact;
            if (!lineTransectSegmentCounted) {
                return Object.assign(Object.assign({}, style), { fillColor: "#444444" });
            }
        };
        this.getTooltip = (lineIdx, content) => {
            const { translations } = this.props.formContext;
            if (this.props.errorSchema[lineIdx]) {
                content = `${content}<br/><span class="text-danger">${translations.LineTransectSegmentHasErrors}!</span>`;
            }
            const { gatheringFact = {} } = this.props.formData[lineIdx];
            const { lineTransectSegmentCounted } = gatheringFact;
            if (!lineTransectSegmentCounted)
                content = `${content}<br/>${translations.LineTransectSegmentNotCounted}`;
            return content;
        };
        this.focusOnMap = (idx, initialCall) => {
            if (!this.hasLineTransectFeature(this.props)) {
                setTimeout(() => this.map.zoomToData({ paddingInMeters: 200 }));
                return;
            }
            this.props.formContext.setTimeout(() => {
                if (!this.map) {
                    return;
                }
                const bounds = L.featureGroup(this.map._corridorLayers[idx]).getBounds();
                if (initialCall) {
                    this.map.fitBounds(bounds, { paddingInMeters: 100 });
                }
                else if (bounds.isValid()) {
                    this.map.map.panTo(bounds.getCenter());
                }
            });
        };
        this.state = { showLTTools: false };
        this.props.onChange(syncLineTransectStartEnd(this.props.formData));
    }
    componentDidMount() {
        this.focusOnMap(this.state.idx);
    }
    getOptions() {
        const { formData, disabled, readonly } = this.props;
        const { geometryField, placeholderGeometry } = (0, utils_2.getUiOptions)(this.props.uiSchema);
        const lineTransect = this.hasLineTransectFeature(this.props)
            ? { type: "MultiLineString", coordinates: formData.map(item => item[geometryField].coordinates) }
            : undefined;
        return {
            lineTransect: lineTransect ? {
                feature: { geometry: lineTransect },
                activeIdx: this.state.activeIdx,
                onChange: this.onChange,
                getFeatureStyle: this.getFeatureStyle,
                getTooltip: this.getTooltip,
                editable: !disabled && !readonly
            } : undefined,
            draw: lineTransect ? false : {
                line: true,
                marker: false,
                circle: false,
                rectangle: false,
                polygon: false,
                onChange: this.onLineCreate
            },
            data: lineTransect || !placeholderGeometry ? false : {
                geoData: placeholderGeometry,
                getFeatureStyle: this._getPlaceholderStyle
            },
            controls: {
                lineTransect: {
                    split: true,
                    splitByMeters: this.state.showLTTools,
                    createPoint: this.state.showLTTools,
                    shiftPoint: this.state.showLTTools,
                    deletePoints: this.state.showLTTools,
                    undo: this.state.showLTTools,
                    redo: this.state.showLTTools
                },
                draw: {
                    undo: false,
                    redo: false
                }
            },
            customControls: [{
                    iconCls: `glyphicon glyphicon-${this.state.showLTTools ? "menu-up" : "option-vertical"}`,
                    fn: () => {
                        this.setState({ showLTTools: !this.state.showLTTools });
                    },
                    text: this.props.formContext.translations[this.state.showLTTools ? "HideLineTransectControls" : "ShowLineTransectControls"],
                    group: "lineTransect"
                }]
        };
    }
    afterActiveChange(idx, initialCall) {
        this.focusOnMap(idx, initialCall);
    }
    hasLineTransectFeature(props) {
        const { geometryField } = (0, utils_2.getUiOptions)(props.uiSchema);
        return props.formData && props.formData[0] && Object.keys(props.formData[0][geometryField] || {}).length;
    }
    componentDidUpdate(prevProps) {
        if (!this.hasLineTransectFeature(prevProps) || !this.hasLineTransectFeature(this.props))
            return;
        for (let lineIdx = 0; lineIdx < this.props.formData.length; lineIdx++) {
            this.map._updateLTStyleForLineIdx(lineIdx);
        }
    }
};
LineTransectMapArrayField.contextType = ReactContext_1.default;
LineTransectMapArrayField = __decorate([
    _MapArrayField
], LineTransectMapArrayField);
let LolifeMapArrayField = class LolifeMapArrayField extends React.Component {
    constructor(props) {
        super(props);
        this.onChangeForGathering = (idx) => (events) => {
            let formData = this.getGatherings ? this.getGatherings() : this.props.formData;
            events.forEach(e => {
                switch (e.type) {
                    case "edit":
                        formData = (0, immutability_helper_1.default)(formData, formData[idx].geometry.type === "GeometryCollection"
                            ? Object.keys(e.features).reduce((updates, _idx) => (Object.assign(Object.assign({}, updates), { [idx]: { geometry: { geometries: { [_idx]: { $set: e.features[_idx].geometry } } } } })), {})
                            : { [idx]: { geometry: { $set: e.features[0].geometry } } });
                        break;
                    case "create":
                        formData = (0, immutability_helper_1.default)(formData, { [idx]: { geometry: { $set: e.feature.geometry } } });
                        break;
                    case "delete":
                        if (formData[idx].geometry.type === "GeometryCollection") {
                            let splices = [];
                            e.idxs.sort().reverse().forEach((idx) => {
                                splices.push([idx, 1]);
                            });
                            formData = (0, immutability_helper_1.default)(formData, { [idx]: { geometry: { geometries: { $splice: splices } } } });
                        }
                        else {
                            formData = (0, immutability_helper_1.default)(formData, { [idx]: { geometry: { $set: undefined } } });
                        }
                        break;
                }
            });
            this.props.onChange(formData);
        };
        this.onChangeForUnits = (events) => {
            let formData = this.props.formData[0].units;
            events.forEach(e => {
                switch (e.type) {
                    case "edit":
                        formData = (0, utils_2.updateSafelyWithJSONPointer)(formData, e.features[0].geometry, `/${e.features[0].properties.idx}/unitGathering/geometry`);
                        break;
                    case "create":
                        throw new Error("Lolife map shouldn't me able to send create for unit");
                    case "delete":
                        formData = (0, utils_2.updateSafelyWithJSONPointer)(formData, undefined, `/${e.features[0].properties.idx}/unitGathering/geometry`);
                        break;
                }
            });
            this.props.onChange((0, utils_2.updateSafelyWithJSONPointer)(this.props.formData, formData, "/0/units"));
        };
        this.getFeatureStyle = ({ feature }, highlight) => {
            if (!feature) {
                return {};
            }
            const namedPlaceStyle = { color: "#777777", fillOpacity: 0.2, weight: 6 };
            const foragingStyle = { color: "#FFCD38" };
            const breedingAndRestingStyle = { color: "#a9d18e" };
            const accessStyle = { color: "#F489A7" };
            const coreZoneStyle = { color: "#F2764D" };
            const habitatZoneStyle = { color: "#937D32" };
            const applicableZoneStyle = { color: "#8EC0D1" };
            let idx = undefined;
            const gathering = this.props.formData.find((item, _idx) => {
                idx = _idx;
                return (0, utils_2.getUUID)(item) === feature.properties.id;
            });
            if (!gathering) {
                return {};
            }
            let style;
            const { gatheringType } = gathering;
            switch (gatheringType) {
                case "MY.gatheringTypeForagingArea":
                    style = foragingStyle;
                    break;
                case "MY.gatheringTypeBreedingAndRestingArea":
                    style = breedingAndRestingStyle;
                    break;
                case "MY.gatheringTypeLolifeAccess":
                    style = accessStyle;
                    break;
                case "MY.gatheringTypeLolifeCoreZone":
                    style = coreZoneStyle;
                    break;
                case "MY.gatheringTypeLolifeHabitatZone":
                    style = habitatZoneStyle;
                    break;
                case "MY.gatheringTypeLolifeApplicableZone":
                    style = applicableZoneStyle;
                    break;
                default:
                    style = namedPlaceStyle;
            }
            const { activeIdx } = this.state;
            if (highlight || activeIdx !== undefined) {
                if (idx !== activeIdx) {
                    return (0, exports.getFeatureStyleWithLowerOpacity)(style);
                }
                return (0, exports.getFeatureStyleWithHighlight)(style);
            }
            return style;
        };
        this.getUnitFeatureStyle = ({ feature }) => {
            const droppingsTreeStyle = { color: "#b89220" };
            const nestTreeStyle = { color: "#9e713b" };
            const observationStyle = { color: globals_1.NORMAL_COLOR };
            const unit = this.props.formData[0].units.find((item) => (0, utils_2.getUUID)(item) === feature.properties.id);
            if (!unit) {
                return {};
            }
            const { nestType, indirectObservationType } = unit;
            let style = observationStyle;
            if (nestType) {
                style = nestTreeStyle;
            }
            else if (indirectObservationType) {
                style = droppingsTreeStyle;
            }
            const { activeIdx } = this.state;
            if (activeIdx !== undefined) {
                return (0, exports.getFeatureStyleWithLowerOpacity)(style);
            }
            return style;
        };
        this.startHighlight = ({ id }) => {
            let mapIdx = this.map.data.findIndex(d => ((d.featureCollection.features[0] || {}).properties || {}).id === id);
            let style = this.map.data[mapIdx].getFeatureStyle({ dataIdx: mapIdx, feature: this.map.data[mapIdx].featureCollection.features[0] });
            if (!style.color) {
                return;
            }
            const color = (0, utils_1.combineColors)(style.color, "#ffffff", 150);
            const layer = this.map.getLayerByIdxTuple([mapIdx, 0]);
            layer && this.map.setLayerStyle(layer, Object.assign(Object.assign({}, style), { color, fillColor: color }));
        };
        this.endHighlight = ({ id }) => {
            let mapIdx = this.map.data.findIndex(d => ((d.featureCollection.features[0] || {}).properties || {}).id === id);
            const style = this.map.data[mapIdx].getFeatureStyle({ dataIdx: mapIdx, feature: this.map.data[mapIdx].featureCollection.features[0] });
            if (!style) {
                return;
            }
            if (!style.fillOpacity) {
                style.fillOpacity = 0.4; // LajiMap default fill opacity.
            }
            const layer = this.map.getLayerByIdxTuple([mapIdx, 0]);
            layer && this.map.setLayerStyle(layer, style);
        };
        this.onClick = (e, { feature }) => {
            const { idx, unit } = feature.properties;
            if (!unit && !idx) {
                return;
            }
            const { topOffset, bottomOffset } = this.props.formContext;
            if (!unit) {
                this.setState({ activeIdx: idx });
            }
            const elem = this.getHighlightElem(idx, unit);
            (0, utils_2.scrollIntoViewIfNeeded)(elem, topOffset, bottomOffset);
            (0, utils_2.highlightElem)(elem);
        };
        this.onMouseOver = this.onMouseOver.bind(this);
        this.onMouseOut = this.onMouseOut.bind(this);
        this.startHighlight = this.startHighlight.bind(this);
        this.endHighlight = this.endHighlight.bind(this);
        this.onChanges = {};
    }
    componentDidMount() {
        this.props.formContext.services.customEvents.add(this.props.idSchema.$id, "startHighlight", this.startHighlight);
        this.props.formContext.services.customEvents.add(this.props.idSchema.$id, "endHighlight", this.endHighlight);
    }
    componentWillUnmount() {
        this.props.formContext.services.customEvents.remove(this.props.idSchema.$id, "startHighlight", this.startHighlight);
        this.props.formContext.services.customEvents.remove(this.props.idSchema.$id, "endHighlight", this.endHighlight);
    }
    componentDidUpdate(prevProps, prevState) {
        if (this.highlightedElem && prevState.activeIdx !== this.state.activeIdx) {
            this.highlightedElem.className = this.highlightedElem.className.replace(" map-highlight", "");
        }
    }
    getOptions() {
        const data = this.getData();
        return {
            draw: this.getDraw(),
            data,
            controls: {
                draw: {
                    undo: false,
                    redo: false
                }
            }
        };
    }
    getDraftStyle() {
        return this.getFeatureStyle({ feature: { properties: { id: (0, utils_2.getUUID)(this.props.formData[this.state.activeIdx]) } } }, !!"higlight");
    }
    getDraw() {
        const active = this.props.formData[this.state.activeIdx];
        if (!active) {
            return false;
        }
        return {
            geoData: this.getFeatureCollection(active, { idx: this.state.activeIdx }),
            onChange: this.getOnChangeForIdx(this.state.activeIdx),
            getFeatureStyle: this.getFeatureStyle,
            getDraftStyle: this.getDraftStyle,
            marker: false,
            on: {
                mouseover: this.onMouseOver,
                mouseout: this.onMouseOut,
            },
        };
    }
    getOnChangeForIdx(idx) {
        // Store so change detection doesn't think it's a new function.
        const onChangeForIdx = this.onChanges[idx] || this.onChangeForGathering(idx);
        this.onChanges[idx] = onChangeForIdx;
        return onChangeForIdx;
    }
    getFeatureCollection(geometryContainer, properties = {}, geometryField = "/geometry") {
        return { type: "FeatureCollection", features: parseGeometries((0, utils_2.parseJSONPointer)(geometryContainer, geometryField)).map(g => ({ type: "Feature", properties: Object.assign({ id: (0, utils_2.getUUID)(geometryContainer) }, properties), geometry: g })) };
    }
    getData() {
        return this._getData(this.props.formData);
    }
    _getData(formData) {
        let gatherings = (formData || []).map((gathering, idx) => ({
            featureCollection: this.getFeatureCollection(gathering, { idx }),
            getFeatureStyle: this.getFeatureStyle,
            on: {
                mouseover: this.onMouseOver,
                mouseout: this.onMouseOut,
                click: this.onClick
            },
            onChange: idx && this.getOnChangeForIdx(idx),
            editable: !!idx,
            getPopup: idx && this.getPopup
        }));
        const { activeIdx } = this.state;
        if (activeIdx !== undefined) {
            gatherings.splice(activeIdx, 1);
        }
        const units = (formData[0].units || []).map((unit, idx) => ({
            featureCollection: this.getFeatureCollection(unit, { unit: true, idx }, "/unitGathering/geometry"),
            editable: true,
            onChange: this.onChangeForUnits,
            getFeatureStyle: this.getUnitFeatureStyle,
            on: {
                mouseover: this.onMouseOver,
                mouseout: this.onMouseOut,
                click: this.onClick
            },
            getPopup: this.getPopup
        })).filter(item => item.featureCollection.features.length);
        return [...gatherings, ...units];
    }
    getGeometries() {
        return this._getGeometries(this.getData());
    }
    _getGeometries(data) {
        return data.reduce((geometries, { featureCollection }) => [
            ...geometries,
            ...featureCollection.features.map(f => f.geometry)
        ], []);
    }
    cavityTreeStyle() { return { color: "#9e713b" }; }
    droppingsTreeStyle() { return { color: "#b89220" }; }
    nestTreeStyle() { return { color: "#ff0000" }; }
    observationStyle() { return { color: globals_1.NORMAL_COLOR }; }
    getHighlightElem(idx, unit) {
        if (unit) {
            return this.props.formContext.utils.getSchemaElementById(`${this.props.idSchema.$id}_0_units_${idx}`);
        }
        else {
            return document.getElementById(`${this.props.idSchema.$id}_${idx}-panel`);
        }
    }
    onMouseOver(e, { feature }) {
        const { idx, unit } = feature.properties;
        if (!unit && !idx) {
            return;
        }
        this.highlightedElem = this.getHighlightElem(idx, unit);
        if (this.highlightedElem) {
            this.highlightedElem.className += " map-highlight";
        }
    }
    onMouseOut(e, { feature }) {
        const { idx, unit } = feature.properties;
        if (!unit && !idx) {
            return;
        }
        this.highlightedElem = this.getHighlightElem(idx, unit);
        if (this.highlightedElem) {
            this.highlightedElem.className = this.highlightedElem.className.replace(" map-highlight", "");
        }
    }
    getFormDataForPopup({ feature }) {
        if (feature.properties.unit) {
            return this.props.formData[0].units.find(item => (0, utils_2.getUUID)(item) === feature.properties.id);
        }
        return this.props.formData.find(item => (0, utils_2.getUUID)(item) === feature.properties.id);
    }
};
LolifeMapArrayField = __decorate([
    _MapArrayField
], LolifeMapArrayField);
function _MapArrayField(ComposedComponent) {
    class _MapArrayField extends ComposedComponent {
        constructor(props) {
            super(props);
            this.stretchContainerRef = React.createRef();
            this.geocode = () => __awaiter(this, void 0, void 0, function* () {
                // Zoom map to area. Area ID is accessed from schema field defined in options.areaField
                const item = (this.props.formData || [])[this.state.activeIdx];
                const { areaField } = (0, utils_2.getUiOptions)(this.props.uiSchema);
                if (!item || !areaField) {
                    return;
                }
                let area = item[areaField];
                if (area instanceof Array) {
                    area = area[0];
                }
                const geometries = this.getGeometries();
                if (geometries.length === 0 && area && area.length > 0) {
                    const { name } = yield this.props.formContext.apiClient.get(`/areas/${area}`);
                    this.map.geocode(name, undefined, 8);
                }
            });
            this.computeArea = () => {
                const { activeIdx } = this.state;
                if (activeIdx === undefined)
                    return;
                let { computeAreaField, areaInHectares } = (0, utils_2.getUiOptions)(this.props.uiSchema);
                const { formData } = this.props;
                if (!computeAreaField)
                    return;
                const geometries = this.getGeometries();
                const polygonsArea = geometries
                    .filter(({ type }) => type === "Polygon")
                    .map(({ coordinates }) => coordinates[0].slice(1).map(c => L.latLng(c.slice(0).reverse())))
                    .reduce((area, latLngs) => area + L.GeometryUtil.geodesicArea(latLngs), 0);
                const circlesArea = geometries
                    .filter(({ type, radius }) => type === "Point" && radius)
                    .reduce((_area, { radius }) => _area + (Math.PI) * (radius * radius), 0);
                const sumArea = polygonsArea + circlesArea;
                const area = sumArea === 0
                    ? undefined
                    : Math.round(areaInHectares ? sumArea / 10000 : sumArea);
                const currentArea = (0, utils_2.parseJSONPointer)(formData[activeIdx], computeAreaField, true);
                currentArea !== area && this.props.onChange((0, utils_2.updateSafelyWithJSONPointer)(formData, area, `/${activeIdx}/${computeAreaField}`));
            };
            this.onResize = () => this.refs.map.map.map.invalidateSize({ debounceMoveend: true });
            this.onPopupClose = () => {
                // Move popup content back to the React container so React won't crash.
                if (this.refs.popupContainer && this.refs.popup.refs.popup) {
                    (0, react_dom_1.findDOMNode)(this.refs.popupContainer).appendChild(this.refs.popup.refs.popup);
                }
                this.setState({ popupIdx: undefined });
            };
            this.onFocusGrab = () => this.setState({ focusGrabbed: true });
            this.onFocusRelease = () => this.setState({ focusGrabbed: false });
            this.onOptionsChanged = (options) => this.setState({ mapOptions: Object.assign(Object.assign({}, this.state.mapOptions), options) }, () => {
                if (this._tileLayerNameOnNextTickCallback) {
                    this._tileLayerNameOnNextTickCallback();
                    this._tileLayerNameOnNextTickCallback = undefined;
                }
            });
            this.onEnterViewPort = () => {
                this.afterActiveChange(this.state.activeIdx, !!"initial call");
            };
            this.getMapOptions = () => {
                const options = (0, utils_2.getUiOptions)(this.props.uiSchema);
                let _options = deepmerge_1.default.all([
                    { clickBeforeZoomAndPan: true },
                    (options.mapOptions || {}),
                    (this.getOptions(options) || {}),
                    (this.state.mapOptions || {})
                ]);
                const changes = {};
                if (options.createOnLocate && !_options.locate) {
                    changes.locate = [this.onLocate];
                }
                const { readonly, disabled } = this.props;
                if (readonly || disabled) {
                    if ((0, utils_2.isObject)(_options.draw)) {
                        changes.draw = Object.assign(Object.assign({}, _options.draw), { editable: false });
                    }
                    if (_options.data) {
                        if (_options.data instanceof Array) {
                            changes.data = _options.data.map(d => (Object.assign(Object.assign({}, d), { editable: false })));
                        }
                        else if ((0, utils_2.isObject)(_options.data)) {
                            changes.data = Object.assign(Object.assign({}, _options.data), { editable: false });
                        }
                    }
                }
                if (Object.keys(changes).length) {
                    _options = Object.assign(Object.assign({}, _options), changes);
                }
                return _options;
            };
            this.onActiveChange = (idx, prop, callback) => {
                this.nestedHandledActiveChange = true;
                this.setState({ activeIdx: idx }, () => {
                    if (!callback)
                        return;
                    callback();
                });
            };
            this.customAdd = () => () => {
                const nextActive = this.props.formData.length;
                this.props.onChange((0, ArrayField_1.onArrayFieldChange)([...this.props.formData, (0, utils_2.getDefaultFormState)(this.props.schema.items)], this.props));
                this.setState({ activeIdx: nextActive });
            };
            this.mapKeyFunctions = {
                splitLineTransectByMeters: () => {
                    this.map.splitLTByMeters(this.state.activeIdx);
                }
            };
            this.getPopup = (options, openPopupCallback) => {
                if (!this.refs.popup)
                    return;
                this.setState({ popupIdx: this.filterPopupOptions(options) }, () => {
                    this.refs.popup && (0, utils_2.hasData)(this.getPopupData(options)) && openPopupCallback(this.refs.popup.refs.popup);
                });
            };
            this.popupStrategies = {
                lolifeUnit: (formData) => {
                    const { nestType, indirectObservationType, identifications } = formData;
                    if (nestType) {
                        return { value: this.props.formContext.translations.NestObservation };
                    }
                    else if (indirectObservationType) {
                        return { value: this.props.formContext.translations.TraceObservation };
                    }
                    else if (identifications) {
                        return { value: this.props.formContext.translations.Observation };
                    }
                }
            };
            this.getFeaturePopupData = (options) => {
                if (!options)
                    return [];
                const { popupFields } = (0, utils_2.getUiOptions)(this.props.uiSchema);
                const formData = this.getFormDataForPopup(options);
                let data = [];
                popupFields.forEach(({ field: col, template, value: _value, title: _title, if: _if, strategy }) => {
                    let value, title;
                    if (strategy) {
                        const strategyResult = this.popupStrategies[strategy](formData);
                        if (strategyResult) {
                            value = strategyResult.value;
                            title = strategyResult.title;
                        }
                    }
                    else if (_value) {
                        value = _value;
                        title = _title;
                    }
                    else if (col) {
                        if (this.parsePopupPointer) {
                            col = this.parsePopupPointer(col, options);
                        }
                        const _formData = (0, utils_2.parseJSONPointer)(formData, col);
                        const schema = (0, utils_2.parseSchemaFromFormDataPointer)(this.props.schema.items || this.props.schema, col);
                        const uiSchema = (0, utils_2.parseUiSchemaFromFormDataPointer)(this.props.uiSchema.items || this.props.uiSchema, col);
                        title = schema.title;
                        value = (0, utils_2.formatValue)(Object.assign(Object.assign({}, this.props), { formData: _formData, schema, uiSchema }));
                    }
                    if (!(0, utils_2.isEmptyString)(value)) {
                        let result;
                        if (_if) {
                            result = ["dataIdx", "featureIdx"].every(opt => !(opt in _if) || options[opt] === _if[opt]);
                            if (_if.reverse) {
                                result = !result;
                            }
                        }
                        if (!_if || result) {
                            data.push({ title, template, value });
                        }
                    }
                });
                return data;
            };
            this.onLocate = (latlng, radius) => {
                this.location = latlng ? { latlng, radius } : undefined;
                this.onLocateOrAddNew();
            };
            this.onLocateOrAddNew = () => {
                if (!this.location)
                    return;
                const { latlng, radius } = this.location;
                const { createOnLocate } = (0, utils_2.getUiOptions)(this.props.uiSchema);
                if (!createOnLocate)
                    return;
                const geometry = this.getGeometry(this.props.formData);
                if (this.props.formData.length === 0 || (!geometry || !Object.keys(geometry).length)) {
                    let geometry = undefined;
                    if (createOnLocate === "marker") {
                        geometry = { type: "Point", coordinates: [latlng.lng, latlng.lat] };
                    }
                    if (createOnLocate === "circle") {
                        geometry = { type: "Point", coordinates: [latlng.lng, latlng.lat], radius };
                    }
                    this.map.addFeatureToDraw({
                        type: "Feature",
                        properties: {},
                        geometry
                    });
                }
            };
            this.props.formContext.services.settings.bind(this, props);
            this._context = (0, Context_1.default)(`${props.formContext.contextId}_MAP_CONTAINER`);
            this._context.featureIdxsToItemIdxs = {};
            this._context.setState = (state, callback) => this.setState(state, callback);
            const initialState = Object.assign(Object.assign({}, (this.state || {})), { activeIdx: (this.props.formData || []).length === 1 ? 0 : undefined });
            const options = (0, utils_2.getUiOptions)(props.uiSchema);
            if ((this.props.formData || []).length && "activeIdx" in options)
                initialState.activeIdx = options.activeIdx;
            this.state = Object.assign(Object.assign({}, initialState), (this.state || {}));
            this.getDraftStyle = this.getDraftStyle.bind(this);
        }
        componentDidMount() {
            if (super.componentDidMount)
                super.componentDidMount();
            this.setState({ mounted: true });
            this.props.formContext.services.keyHandler.addKeyHandler(`${this.props.idSchema.$id}`, this.mapKeyFunctions);
            this.map = this.refs.map.refs.map.map;
            this._setActiveEventHandler = idx => {
                this.setState({ activeIdx: idx });
            };
            this._zoomToDataEventHandler = () => {
                this._zoomToDataOnNextTick = true;
            };
            this._tileLayersEventHandler = (tileLayerOptions, callback) => {
                this._tileLayerNameOnNextTick = tileLayerOptions;
                this._tileLayerNameOnNextTickCallback = callback;
            };
            this.props.formContext.services.customEvents.add(this.props.idSchema.$id, "activeIdx", this._setActiveEventHandler);
            this.props.formContext.services.customEvents.add(this.props.idSchema.$id, "zoomToData", this._zoomToDataEventHandler);
            this.props.formContext.services.customEvents.add(this.props.idSchema.$id, "tileLayers", this._tileLayersEventHandler);
            if (this.state.activeIdx !== undefined) {
                this.afterActiveChange(this.state.activeIdx, !!"initial call");
            }
        }
        afterActiveChange(idx, initialCall) {
            super.afterActiveChange && super.afterActiveChange(idx, initialCall);
            this.onLocateOrAddNew();
        }
        componentWillUnmount() {
            this.setState({ mounted: false });
            this.props.formContext.services.keyHandler.removeKeyHandler(`${this.props.idSchema.$id}`, this.mapKeyFunctions);
            this.props.formContext.services.customEvents.remove(this.props.idSchema.$id, "activeIdx", this._setActiveEventHandler);
            this.props.formContext.services.customEvents.remove(this.props.idSchema.$id, "zoomToData", this._zoomToDataEventHandler);
            this.props.formContext.services.customEvents.remove(this.props.idSchema.$id, "tileLayers", this._tileLayersEventHandler);
        }
        componentDidUpdate(...params) {
            if (super.componentDidUpdate)
                super.componentDidUpdate(...params);
            const [prevProps, prevState] = params;
            if (prevState.activeIdx !== this.state.activeIdx) {
                if (!this.nestedHandledActiveChange && this.state.activeIdx !== undefined) {
                    const { idToFocusAfterNavigate, idToScrollAfterNavigate } = (0, utils_2.getUiOptions)(this.props.uiSchema);
                    this.props.formContext.utils.focusAndScroll(idToFocusAfterNavigate || `${this.props.idSchema.$id}_${this.state.activeIdx}`, idToScrollAfterNavigate);
                }
                this.nestedHandledActiveChange = false;
                this.afterActiveChange(this.state.activeIdx);
            }
            if (this.refs.stretch) {
                const { resizeTimeout } = (0, utils_2.getUiOptions)(this.props.uiSchema);
                if (resizeTimeout) {
                    this.props.formContext.setTimeout(this.refs.stretch.update, resizeTimeout);
                }
                else {
                    this.refs.stretch.update();
                }
            }
            if (this._zoomToDataOnNextTick) {
                this._zoomToDataOnNextTick = false;
                this.map.zoomToData();
            }
            if (this._tileLayerNameOnNextTick) {
                const tileLayerName = this._tileLayerNameOnNextTick;
                this._tileLayerNameOnNextTick = false;
                this.map.setTileLayerByName(tileLayerName);
            }
            this.computeArea(prevProps);
        }
        _getPlaceholderStyle() {
            return {
                color: "#999999",
                fillOpacity: 0,
                weight: 8
            };
        }
        getSchemas() {
            if (super.getSchemas)
                return super.getSchemas();
            return this.props;
        }
        render() {
            const { registry: { fields: { SchemaField } } } = this.props;
            let { uiSchema, errorSchema, schema } = this.getSchemas();
            const options = (0, utils_2.getUiOptions)(this.props.uiSchema);
            const { popupFields, geometryField, topOffset, bottomOffset, belowFields, propsToPassToInlineSchema = [], emptyHelp, passActiveIdxToBelow = true } = options;
            let { belowUiSchemaRoot = {}, inlineUiSchemaRoot = {}, idToFocusAfterNavigate, idToScrollAfterNavigate } = options;
            const { activeIdx } = this.state;
            const activeIdxProps = {
                activeIdx,
                onActiveChange: this.onActiveChange,
                idToFocusAfterNavigate,
                idToScrollAfterNavigate
            };
            uiSchema = (0, utils_2.getInnerUiSchema)(uiSchema);
            if ((0, utils_2.getUiOptions)(this.props.uiSchema).buttons) {
                uiSchema = Object.assign(Object.assign({}, uiSchema), { "ui:options": Object.assign(Object.assign({}, uiSchema["ui:options"]), { buttons: [...(uiSchema["ui:options"].buttons || []), ...(0, utils_2.getUiOptions)(this.props.uiSchema).buttons] }) });
            }
            let mapOptions = this.getMapOptions();
            const mapSizes = options.mapSizes || (0, utils_2.getBootstrapCols)(6);
            const schemaSizes = ["lg", "md", "sm", "xs"].reduce((sizes, size) => {
                sizes[size] = 12 - mapSizes[size] || 12;
                return sizes;
            }, {});
            const getChildProps = () => {
                return {
                    schema: schema.items,
                    uiSchema: uiSchema.items || {},
                    idSchema: this.props.registry.schemaUtils.toIdSchema(schema.items, `${this.props.idSchema.$id}_${activeIdx}`),
                    formData: (this.props.formData || [])[activeIdx],
                    errorSchema: this.props.errorSchema[activeIdx] || {}
                };
            };
            const putChildsToParents = (props, key) => {
                if (!this.onChangeFor) {
                    this.onChangeFor = {};
                }
                if (!this.onChangeFor[key]) {
                    this.onChangeFor[key] = formData => {
                        this.props.onChange(formData.map((item, idx) => {
                            return Object.assign(Object.assign({}, (this.props.formData[idx] || (0, utils_2.getDefaultFormState)(this.props.schema.items))), item);
                        }));
                    };
                }
                return Object.assign(Object.assign({}, this.props), { schema: Object.assign(Object.assign({}, schema), { items: props.schema }), uiSchema: Object.assign(Object.assign({}, uiSchema), { items: props.uiSchema }), onChange: this.onChangeFor[key] });
            };
            const defaultProps = Object.assign(Object.assign({}, this.props), { schema, uiSchema });
            const overrideProps = propsToPassToInlineSchema.reduce((_props, field) => {
                _props[field] = this.props[field];
                return _props;
            }, {});
            const childProps = getChildProps();
            const inlineSchemaProps = putChildsToParents((0, NestField_1.getPropsForFields)(childProps, Object.keys(schema.items.properties).filter(field => !(belowFields || []).includes(field))));
            const belowSchemaProps = belowFields ? putChildsToParents((0, NestField_1.getPropsForFields)(childProps, belowFields)) : null;
            const inlineItemsUiSchema = Object.assign(Object.assign({}, uiSchema.items), inlineSchemaProps.uiSchema.items);
            let inlineUiSchema = Object.assign({}, inlineSchemaProps.uiSchema);
            if (inlineUiSchemaRoot) {
                inlineUiSchema = Object.assign(Object.assign({}, inlineUiSchema), inlineUiSchemaRoot);
                inlineUiSchema.items = Object.assign(Object.assign({}, inlineItemsUiSchema), (inlineUiSchemaRoot.items || {}));
            }
            else {
                inlineUiSchema.items = inlineItemsUiSchema;
            }
            let belowUiSchema = belowSchemaProps ? Object.assign(Object.assign({}, belowSchemaProps.uiSchema), belowUiSchemaRoot) : {};
            inlineUiSchema = Object.assign(Object.assign({}, inlineUiSchema), { "ui:options": Object.assign(Object.assign({}, (inlineUiSchema["ui:options"] || {})), activeIdxProps) });
            belowUiSchema = Object.assign(Object.assign({}, belowUiSchema), { "ui:options": Object.assign(Object.assign({}, (belowUiSchema["ui:options"] || {})), (passActiveIdxToBelow ? activeIdxProps : {})) });
            if (!belowUiSchema.items) {
                belowUiSchema.items = {};
            }
            if (!belowUiSchema.items["ui:options"]) {
                belowUiSchema.items["ui:options"] = {};
            }
            belowUiSchema.items["ui:options"].reserveId = false;
            const { buttonsPath, addButtonPath, showErrors = true } = (0, utils_2.getUiOptions)(this.props.uiSchema);
            if (addButtonPath)
                console.warn("addButtonPath option for MapArrayField is deprecated - use buttonsPath instead!");
            let _buttonsPath = buttonsPath || addButtonPath;
            const prependAddButton = (buttons) => {
                const addButton = buttons.find(({ fn }) => fn === "add");
                return [
                    ...buttons.filter(button => button !== addButton),
                    Object.assign(Object.assign({}, (addButton || {})), { fn: this.customAdd, fnName: "add", glyph: "plus", id: this.props.idSchema.$id })
                ];
            };
            let buttons = undefined;
            let renderButtonsBelow = false;
            if ((mapOptions.emptyMode || activeIdx !== undefined) && options.buttons) {
                if (_buttonsPath) {
                    buttons = prependAddButton(options.buttons);
                    belowUiSchema = (0, utils_2.injectButtons)(belowUiSchema, buttons, _buttonsPath);
                    inlineUiSchema["ui:options"].renderAdd = false;
                }
                else if (options.renderButtonsBelow) {
                    buttons = prependAddButton(options.buttons);
                    inlineUiSchema["ui:options"].renderAdd = false;
                    renderButtonsBelow = true;
                }
                else {
                    buttons = options.buttons;
                }
            }
            if (activeIdx === undefined || (!_buttonsPath && !renderButtonsBelow)) {
                inlineUiSchema["ui:options"].buttons = options.buttons || [];
            }
            const inlineSchema = React.createElement(SchemaField, Object.assign({}, defaultProps, inlineSchemaProps, { uiSchema: inlineUiSchema }, overrideProps));
            const belowSchema = belowFields ? React.createElement(SchemaField, Object.assign({}, this.props, belowSchemaProps, { uiSchema: belowUiSchema })) : null;
            buttons = buttons && (!_buttonsPath || mapOptions.emptyMode)
                ? buttons.map(button => (0, ArrayFieldTemplate_1.getButton)(button, {
                    canAdd: mapOptions.emptyMode ? (button.fn !== "add" && button.fnName !== "add") : true,
                    uiSchema: this.props.uiSchema,
                    idSchema: this.props.idSchema,
                    formData: this.props.formData,
                    formContext: this.props.formContext,
                    disabled: this.props.disabled,
                    readonly: this.props.readonly,
                })).filter(button => button)
                : undefined;
            const _errors = errorSchema && (0, utils_2.parseJSONPointer)(errorSchema[activeIdx] || {}, geometryField);
            const errors = _errors
                ? _errors.__errors.map(utils_2.formatErrorMessage)
                : null;
            const errorId = geometryField && geometryField[0] === "/" ? geometryField.replace(/\//g, "_") : `_${geometryField}`;
            const wholeErrorId = `${this.props.idSchema.$id}_${activeIdx}${errorId}`;
            const mapPropsToPass = Object.assign({ formContext: this.props.formContext, onPopupClose: this.onPopupClose, markerPopupOffset: 45, featurePopupOffset: 5, popupOnHover: true, onFocusGrab: this.onFocusGrab, onFocusRelease: this.onFocusRelease, panel: errors && errors.length && showErrors ? {
                    header: this.props.formContext.translations.Error,
                    panelTextContent: React.createElement("div", null, errors),
                    variant: "danger",
                    id: `laji-form-error-container-${wholeErrorId}`
                } : null, draw: false, zoomToData: true, onOptionsChanged: this.onOptionsChanged, fullscreenable: true, help: options.help }, mapOptions);
            const map = (React.createElement(MapComponent, Object.assign({ ref: "map" }, mapPropsToPass)));
            const wrapperProps = {
                containerRef: this.stretchContainerRef,
                topOffset: topOffset === undefined ? this.props.formContext.topOffset : topOffset,
                bottomOffset: bottomOffset === undefined ? this.props.formContext.bottomOffset : bottomOffset,
                onResize: this.onResize,
                mounted: this.state.mounted,
                className: this.state.focusGrabbed ? "pass-block" : "",
                minHeight: (0, utils_2.getUiOptions)(this.props.uiSchema).minHeight,
                onEnterViewPort: this.onEnterViewPort,
                enterViewPortTreshold: 200
            };
            const wrappedMap = (React.createElement(components_1.Stretch, Object.assign({}, wrapperProps, { ref: "stretch" }), map));
            const TitleFieldTemplate = (0, utils_3.getTemplate)("TitleFieldTemplate", this.props.registry, (0, utils_2.getUiOptions)(this.props.uiSchema));
            const { Popover, Row, Col, ButtonToolbar } = this.context.theme;
            return (React.createElement(React.Fragment, null,
                React.createElement(Row, null,
                    React.createElement(Col, Object.assign({}, mapSizes), wrappedMap),
                    React.createElement(Col, Object.assign({}, schemaSizes, { ref: this.stretchContainerRef }), mapOptions.emptyMode ?
                        (!emptyHelp && (!buttons || !buttons.length) ? null :
                            React.createElement(Popover, { placement: "right", id: `${this.props.idSchema.$id}-help` }, React.createElement("div", null,
                                emptyHelp,
                                buttons && buttons.length ? ` ${this.props.formContext.translations.or}` : null,
                                buttons))) :
                        inlineSchema)),
                popupFields ?
                    React.createElement("div", { style: { display: "none" }, ref: "popupContainer" },
                        React.createElement(Popup, { data: this.getPopupData(this.state.popupIdx), ref: "popup" })) : null,
                React.createElement(Row, null, mapOptions.emptyMode ? null : belowSchema),
                renderButtonsBelow && !mapOptions.emptyMode && buttons.length ? (React.createElement(Row, { className: "map-array-field-below-buttons" },
                    React.createElement(TitleFieldTemplate, { title: (0, utils_2.getUiOptions)(uiSchema).buttonsTitle, schema: this.props.schema }),
                    React.createElement(ButtonToolbar, null, buttons))) : null));
        }
        getDraftStyle() {
            if (super.getDraftStyle) {
                return super.getDraftStyle();
            }
            return { color: "#25B4CA", opacity: 1 };
        }
        filterPopupOptions({ dataIdx, featureIdx, feature }) {
            return { dataIdx, featureIdx, feature };
        }
        getPopupData(options) {
            if (super.getPopupData) {
                return super.getPopupData(options);
            }
            else {
                return this.getFeaturePopupData(options);
            }
        }
        getFormDataForPopup(options) {
            if (super.getFormDataForPopup) {
                return super.getFormDataForPopup(options);
            }
            else {
                return this.state.activeIdx !== undefined ? this.props.formData[this.state.activeIdx] : undefined;
            }
        }
    }
    _MapArrayField.contextType = ReactContext_1.default;
    return _MapArrayField;
}
class Popup extends React.Component {
    render() {
        const { data } = this.props;
        return data && data.length &&
            React.createElement("ul", { ref: "popup", className: "map-data-tooltip" }, data.map(({ value, title, template }, i) => {
                switch (template) {
                    case "title":
                        return React.createElement("li", { key: title || i },
                            React.createElement("strong", null, Array.isArray(value) ? value.join(", ") : value));
                    case "description":
                        return React.createElement("li", { key: title || i },
                            React.createElement("i", null, Array.isArray(value) ? value.join(", ") : value));
                    default:
                        return React.createElement("li", { key: title || i },
                            React.createElement("strong", null,
                                title,
                                ":"),
                            " ",
                            Array.isArray(value) ? value.join(", ") : value);
                }
            }));
    }
}
class MapComponent extends React.Component {
    constructor(props) {
        super(props);
        this.tileLayerChange = ({ tileLayers }) => {
            this.setState({ mapOptions: Object.assign(Object.assign({}, this.state.mapOptions), { tileLayers }) });
        };
        this.overlaysChange = ({ overlayNames }) => {
            this.setState({ mapOptions: Object.assign(Object.assign({}, this.state.mapOptions), { overlayNames }) });
        };
        this.tileLayerOpacityChangeEnd = ({ tileLayerOpacity }) => {
            this.setState({ mapOptions: Object.assign(Object.assign({}, this.state.mapOptions), { tileLayerOpacity }) });
        };
        this.locateToggle = ({ locate }) => {
            if (this.props.onLocateToggle) {
                this.props.onLocateToggle(locate.on); // propagate to parent
            }
            else {
                this.setState({ mapOptions: Object.assign(Object.assign({}, this.state.mapOptions), { locate }) });
            }
        };
        this.grabFocus = () => {
            this.props.formContext.services.blocker.push();
            this.setState({ focusGrabbed: true }, () => {
                if (this.props.onFocusGrab)
                    this.props.onFocusGrab();
            });
        };
        this.releaseFocus = () => {
            this.props.formContext.services.blocker.push();
            this.setState({ focusGrabbed: false }, () => {
                if (this.props.onFocusRelease)
                    this.props.onFocusRelease();
            });
        };
        this.showPanel = (options) => {
            this.setState(Object.assign({ panel: true }, options));
        };
        this.hidePanel = () => {
            this.setState({ panel: false });
        };
        this.showHelp = () => {
            this.setState({ showHelp: true });
        };
        this.hideHelp = () => {
            this.setState({ showHelp: false });
        };
        this.setMapState = (options, callback) => {
            this._callback = callback;
            this.setState({ mapOptions: options });
        };
        this.setOnUpdateMap = (fn) => {
            this._permaCallback = fn;
        };
        this.state = { mapOptions: {} };
        this.mainContext = (0, Context_1.default)(props.formContext.contextId);
        this._context = (0, Context_1.default)(`${props.formContext.contextId}_MAP`);
        this._context.grabFocus = this.grabFocus;
        this._context.releaseFocus = this.releaseFocus;
        this._context.showPanel = this.showPanel;
        this._context.hidePanel = this.hidePanel;
        this._context.setMapState = this.setMapState;
        this._context.setOnUpdateMap = this.setOnUpdateMap;
    }
    componentDidMount() {
        this.map = this.refs.map.map;
        const { map } = this.map;
        this._context.map = this.map;
        map.on("tileLayersChange", this.tileLayerChange);
        map.on("locateToggle", this.locateToggle);
    }
    componentWillUnmount() {
        const { map } = this.map;
        if (!map)
            return;
        map.off("tileLayersChange", this.tileLayerChange);
        map.off("locateToggle", this.locateToggle);
    }
    componentDidUpdate(prevProps, prevState) {
        if (this._callback)
            this._callback();
        this._callback = undefined;
        if (this.props.onOptionsChanged && ["tileLayers", "locate"].some(name => !equals(...[this.state, prevState].map(state => state.mapOptions[name])))) {
            this.props.onOptionsChanged(this.state.mapOptions);
        }
        if (this._permaCallback)
            this._permaCallback();
    }
    render() {
        const _a = this.props, { panel, onFocusGrab, onFocusRelease, onOptionsChanged } = _a, mapOptions = __rest(_a, ["panel", "onFocusGrab", "onFocusRelease", "onOptionsChanged"]);
        const { Modal } = this.context.theme;
        const controlledPanel = panel ?
            React.createElement(MapPanel, { id: panel.id, variant: panel.variant || undefined, buttonThemeRole: panel.buttonThemeRole, header: panel.header, text: panel.panelTextContent })
            : null;
        return (React.createElement("div", { className: "laji-form-map-container" + (this.state.focusGrabbed ? " pass-block" : "") },
            controlledPanel,
            this.state.panel &&
                React.createElement(MapPanel, { id: panel.id, show: this.state.panel, text: this.state.panelTextContent, onClick: this.state.panelButtonOnClick, buttonText: this.state.panelButtonContent, buttonThemeRole: this.state.panelButtonBsStyle }),
            this.state.showHelp &&
                React.createElement(Modal, { dialogClassName: "laji-form", show: true, onHide: this.hideHelp },
                    React.createElement(Modal.Header, { closeButton: true }),
                    React.createElement(Modal.Body, null,
                        React.createElement("span", { dangerouslySetInnerHTML: { __html: mapOptions.help } }))),
            React.createElement(Map, Object.assign({ className: this.props.className, style: this.props.style, ref: "map", showHelp: this.showHelp, hideHelp: this.hideHelp, formContext: this.props.formContext }, mapOptions, this.state.mapOptions))));
    }
}
exports.MapComponent = MapComponent;
MapComponent.contextType = ReactContext_1.default;
class Map extends React.Component {
    constructor(props) {
        super(props);
        this.getMapOptions = (props) => {
            const { className, style, hidden, singleton, emptyMode, onComponentDidMount, fullscreenable, formContext, controlSettings } = props, mapOptions = __rest(props, ["className", "style", "hidden", "singleton", "emptyMode", "onComponentDidMount", "fullscreenable", "formContext", "controlSettings"]);
            return mapOptions;
        };
        this.getEnhancedMapOptions = (props) => {
            const mapOptions = this.getMapOptions(props);
            const { fullscreenable, help, formContext = {} } = props;
            if (fullscreenable) {
                mapOptions.controls = Object.assign({ fullscreen: true }, ((0, utils_2.isObject)(mapOptions.controls)
                    ? mapOptions.controls
                    : {}));
            }
            if (help) {
                mapOptions.customControls = [
                    ...(mapOptions.customControls || []),
                    {
                        iconCls: "glyphicon glyphicon-question-sign",
                        fn: this.props.showHelp,
                        position: "bottomright",
                        text: formContext.translations.Instructions
                    }
                ];
            }
            mapOptions.lang = mapOptions.lang || formContext.lang;
            mapOptions.googleApiKey = formContext.googleApiKey;
            mapOptions.rootElem = this.refs.map;
            const { lajiGeoServerAddress } = props.formContext;
            if (lajiGeoServerAddress) {
                mapOptions.lajiGeoServerAddress = lajiGeoServerAddress;
            }
            return mapOptions;
        };
        this.setMapOptions = (prevOptions, options) => {
            if (!this.map) {
                return;
            }
            const mapOptions = this.getMapOptions(options);
            const prevMapOptions = this.getMapOptions(prevOptions);
            Object.keys(mapOptions).forEach(key => {
                switch (key) {
                    case "draw": // More optimal way of updating draw data than setting the draw option
                        if (!equals(mapOptions.draw, prevMapOptions.draw)) {
                            this.map.updateDrawData(mapOptions.draw);
                        }
                        break;
                    case "rootElem": // deeqEquals on DOM node causes maximum call stack size exceeding.
                        if (mapOptions[key] !== prevMapOptions[key]) {
                            this.map.setOption(key, mapOptions[key]);
                        }
                        break;
                    default:
                        if (!equals(mapOptions[key], prevMapOptions[key])) {
                            this.map.setOption(key, mapOptions[key]);
                            if (this.props.syncZoomToDataOnDataChangeOnSingleton
                                && this.props.singleton
                                && mapOptions.zoomToData && key === "data") {
                                this.map.zoomToData(mapOptions.zoomToData);
                            }
                        }
                }
            });
        };
        this.initializeMap = (props) => {
            const mapOptions = this.getEnhancedMapOptions(props);
            if (props.singleton) {
                const singletonMapService = props.formContext.services.singletonMap;
                this.map = singletonMapService.grab(this, mapOptions);
                this.setMapOptions(this.map.getOptions(), mapOptions);
            }
            else {
                this.map = new laji_map_1.default(mapOptions);
            }
            this.map.map.invalidateSize();
            if (props.onComponentDidMount)
                props.onComponentDidMount(this.map);
        };
        this.state = {};
    }
    componentDidMount() {
        this.mounted = true;
        let options = Object.assign({}, this.props);
        // Backward compatibility for bad settings.
        ["tileLayerName", "overlayNames", "tileLayerOpacity"].forEach(prop => {
            if (prop in options && options[prop] === undefined) {
                options = (0, utils_2.immutableDelete)(options, prop);
            }
        });
        if (this.props.hidden) {
            return;
        }
        this.initializeMap(options);
    }
    componentWillUnmount() {
        if (this.props.singleton) {
            this.props.formContext.services.singletonMap.release(this);
        }
        else {
            this.map && this.map.destroy();
        }
        this.mounted = false;
    }
    componentDidUpdate(prevProps) {
        const { hidden, onComponentDidMount, singleton } = this.props;
        const props = __rest(this.props, []);
        if (!hidden && !this.map) {
            this.initializeMap(props);
            if (onComponentDidMount)
                onComponentDidMount(this.map);
        }
        if (!singleton || this.props.formContext.services.singletonMap.amOwner(this)) {
            if (this.map && prevProps.lineTransect && props.lineTransect && "activeIdx" in props.lineTransect) {
                this.map.setLTActiveIdx(props.lineTransect.activeIdx);
            }
            if (prevProps.lineTransect)
                delete props.lineTransect;
            this.setMapOptions(this.getEnhancedMapOptions(prevProps), this.getEnhancedMapOptions(this.props));
        }
    }
    render() {
        return (React.createElement(React.Fragment, null,
            React.createElement("div", { key: "map", className: "laji-form-map" + (this.props.className ? " " + this.props.className : ""), style: this.props.style, ref: "map" })));
    }
}
exports.Map = Map;
Map.defaultProps = {
    tileLayerName: "maastokartta",
    availableTileLayerNamesBlacklist: ["pohjakartta"]
};
class MapPanel extends React.Component {
    render() {
        const { Panel } = this.context.theme;
        return (React.createElement(Panel, { variant: this.props.variant || undefined, className: "laji-form-popped", id: this.props.id },
            this.props.header ? (React.createElement(Panel.Heading, null, this.props.header)) : null,
            React.createElement(Panel.Body, null,
                this.props.text,
                this.props.buttonText ?
                    React.createElement(components_1.Button, { variant: this.props.buttonThemeRole || "default", onClick: this.props.onClick }, this.props.buttonText) :
                    null)));
    }
}
MapPanel.contextType = ReactContext_1.default;
const saneOpacityRange = (opacity) => Math.min(1, Math.max(0, opacity || 0));
const getFeatureStyleWithLowerOpacity = style => (Object.assign(Object.assign({}, style), { opacity: saneOpacityRange(style.opacity || 1 - 0.5), fillOpacity: saneOpacityRange(style.fillOpacity || 0.4 - 0.3) }));
exports.getFeatureStyleWithLowerOpacity = getFeatureStyleWithLowerOpacity;
const getFeatureStyleWithHighlight = style => {
    const color = style.color
        ? (0, utils_1.combineColors)(style.color, "#ffffff", 30)
        : undefined;
    return Object.assign(Object.assign({}, style), { color, fillOpacity: saneOpacityRange(style.fillOpacity || 0.4 + 0.4) });
};
exports.getFeatureStyleWithHighlight = getFeatureStyleWithHighlight;
const syncLineTransectStartEnd = (formData) => {
    const lineTransectFeature = Object.keys((formData || [])[0].geometry || {}).length
        ? { type: "Feature", properties: {}, geometry: { type: "MultiLineString", coordinates: formData.map(item => item.geometry.coordinates) } }
        : undefined;
    if (lineTransectFeature) {
        return formData.map((item, idx) => {
            const [start, end] = (0, utils_4.getLineTransectStartEndDistancesForIdx)(lineTransectFeature, idx, 10);
            if (!item.gatheringFact) {
                item.gatheringFact = {};
            }
            item.gatheringFact.lineTransectSegmentMetersStart = start;
            item.gatheringFact.lineTransectSegmentMetersEnd = end;
            return Object.assign(Object.assign({}, item), { gatheringFact: Object.assign(Object.assign({}, (item.gatheringFact || {})), { lineTransectSegmentMetersStart: start, lineTransectSegmentMetersEnd: end }) });
        });
    }
    return formData;
};
