"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 __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.findSingleGeometry = findSingleGeometry;
exports.getCenterAndRadiusFromGeometry = getCenterAndRadiusFromGeometry;
const React = __importStar(require("react"));
const react_dom_1 = require("react-dom");
const PropTypes = __importStar(require("prop-types"));
const MapArrayField_1 = require("./MapArrayField");
const utils_1 = require("../../utils");
const equals = require("deep-equal");
const react_spinner_1 = __importDefault(require("react-spinner"));
const components_1 = require("../components");
const utils_2 = require("@luomus/laji-map/lib/utils");
const utils_3 = require("@rjsf/utils");
function findSingleGeometry(geoJSON) {
    if (!geoJSON)
        return undefined;
    switch (geoJSON.type) {
        case "FeatureCollection":
            return geoJSON.features.length === 1
                ? findSingleGeometry(geoJSON.features[0])
                : undefined;
        case "GeometryCollection":
            return geoJSON.geometries.length === 1
                ? findSingleGeometry(geoJSON.geometries[0])
                : undefined;
        case "Feature":
            return findSingleGeometry(geoJSON.geometry);
        case undefined:
            return undefined;
        default:
            return geoJSON && geoJSON.coordinates ? geoJSON : undefined;
    }
}
function getCenterAndRadiusFromGeometry(geometry) {
    const singleGeometry = findSingleGeometry(geometry);
    if (!singleGeometry) {
        return {};
    }
    let center, radius;
    if (singleGeometry && singleGeometry.type === "Point") {
        center = singleGeometry.coordinates.slice(0).reverse();
        radius = singleGeometry.radius;
    }
    else {
        const bounds = new L.GeoJSON((0, utils_2.anyToFeatureCollection)(geometry)).getBounds();
        center = bounds.getCenter();
        radius = bounds.getSouthWest().distanceTo(bounds.getSouthEast()) / 2;
    }
    return { center, radius };
}
class MapField extends React.Component {
    constructor(props) {
        super(props);
        this.onLocateEventHandler = (geometry) => {
            if (geometry) {
                this.onLocate({ lat: geometry.coordinates[1], lng: geometry.coordinates[0] }, 100, !!"force");
            }
            else if (!this.getGeometry(this.props)) {
                this.setState({ locateOn: true });
            }
        };
        this.geocode = (prevProps) => __awaiter(this, void 0, void 0, function* () {
            let { area } = (0, utils_1.getUiOptions)(this.props.uiSchema);
            if (area instanceof Array) {
                area = area[0];
            }
            const isEmptyAndWasEmpty = [this.props, prevProps].every(props => {
                const { geoData } = this.getDrawOptions(props);
                return !geoData
                    || ((0, utils_1.isObject)(geoData) && Object.keys(geoData).length === 0)
                    || (geoData.type === "GeometryCollection" && geoData.geometries.length === 0)
                    || (geoData.type === "FeatureCollection" && geoData.features.length === 0);
            });
            if (isEmptyAndWasEmpty && area && area.length > 0) {
                const { name } = yield this.props.formContext.apiClient.get(`/areas/${area}`);
                this.map.geocode(name, undefined, 8);
            }
        });
        this.zoomIfExternalEdit = (props) => {
            if (!equals(this._lastFormData, props.formData)) {
                this.map.zoomToData();
            }
        };
        this.setMapRef = (mapComponent) => {
            if (mapComponent && mapComponent.refs && mapComponent.refs.map) {
                this.map = mapComponent.refs.map.map;
                this.setState({ mapRendered: true });
            }
        };
        this.showMobileEditorMap = () => {
            this.setState({ mobileEditor: { visible: true, options: (this.state.mobileEditor || {}).options || {} } });
        };
        this.getDrawOptions = (props) => {
            const { uiSchema, disabled, readonly } = props;
            const options = (0, utils_1.getUiOptions)(uiSchema);
            const { mapOptions = {}, coordinateFields } = options;
            const drawOptions = Object.assign(Object.assign({}, (mapOptions.draw || {})), { geoData: this.getGeometry(props), onChange: this.onChange, editable: !disabled && !readonly && !options.mobileEditor });
            if (coordinateFields) {
                ["rectangle", "polygon", "polyline", "circle"].forEach(drawType => {
                    drawOptions[drawType] = false;
                });
            }
            return drawOptions;
        };
        this.getGeometry = (props) => {
            const { formData } = props;
            const { coordinateFields } = (0, utils_1.getUiOptions)(this.props.uiSchema);
            if (coordinateFields) {
                if ((formData === null || formData === void 0 ? void 0 : formData[coordinateFields.longitude]) !== undefined && (formData === null || formData === void 0 ? void 0 : formData[coordinateFields.latitude]) !== undefined) {
                    return {
                        type: "Point",
                        coordinates: [+formData[coordinateFields.longitude], +formData[coordinateFields.latitude]]
                    };
                }
                else {
                    return undefined;
                }
            }
            return formData && Object.keys(formData).length ? formData : undefined;
        };
        this.getMobileGeometry = () => {
            if (this.props.formData) {
                return this.props.formData;
            }
            else {
                return {
                    type: "Point",
                    coordinates: [24.94782264266911, 60.17522413438655]
                };
            }
        };
        this.getFormDataFromGeometry = (geometry) => {
            const { geometryCollection = true, coordinateFields } = (0, utils_1.getUiOptions)(this.props.uiSchema);
            let formData;
            if (coordinateFields) {
                if (geometry) {
                    let longitude = geometry.coordinates[0];
                    let latitude = geometry.coordinates[1];
                    if (this.props.schema.properties[coordinateFields.longitude].type === "string") {
                        longitude = "" + longitude;
                    }
                    if (this.props.schema.properties[coordinateFields.latitude].type === "string") {
                        latitude = "" + latitude;
                    }
                    formData = {
                        [coordinateFields.longitude]: longitude,
                        [coordinateFields.latitude]: latitude
                    };
                }
                else {
                    formData = undefined;
                }
            }
            else {
                formData = geometryCollection ? {
                    type: "GeometryCollection",
                    geometries: geometry ? [geometry] : []
                } : geometry || {};
            }
            return formData;
        };
        this.setMoved = (moved) => {
            this.setState({ moved });
        };
        this.onOptionsChanged = (options) => {
            this.setState({ mapOptions: Object.assign(Object.assign({}, this.state.mapOptions), options) });
        };
        this.onChange = (events) => {
            let formData;
            events.forEach(e => {
                switch (e.type) {
                    case "create":
                        formData = this.getFormDataFromGeometry(e.feature.geometry);
                        break;
                    case "edit":
                        formData = this.getFormDataFromGeometry(e.features[0].geometry);
                        break;
                    case "delete":
                        formData = this.getFormDataFromGeometry(undefined);
                }
            });
            this._zoomToDataOnNextTick = true;
            this.props.onChange(formData);
        };
        this.onMobileEditorChange = (point) => {
            const geometry = this.getGeometry(this.props);
            const feature = {
                type: "Feature",
                geometry: point
            };
            if (geometry) {
                this.onChange([{ type: "edit", features: [feature] }]);
            }
            else {
                this.onChange([{ type: "create", feature }]);
            }
        };
        this.onHideMobileEditorMap = (options) => {
            this.setState({ mobileEditor: { visible: false, options } });
        };
        this.onLocate = (latlng, radius, forceShow) => {
            const { mobileEditor, createOnLocate } = (0, utils_1.getUiOptions)(this.props.uiSchema);
            const isEmpty = !this.getGeometry(this.props);
            if (!latlng || !isEmpty) {
                this.setState({ located: true });
                return;
            }
            if (mobileEditor) {
                if (!this.state.located || forceShow) {
                    this.setState({
                        mobileEditor: {
                            visible: true,
                            options: (this.state.mobileEditor || {}).options || {}
                        },
                        located: true
                    });
                }
                return;
            }
            this.setState({ located: true });
            if (createOnLocate) {
                const geometry = { type: "Point", coordinates: [latlng.lng, latlng.lat] };
                this.props.onChange(this.getFormDataFromGeometry(geometry));
            }
        };
        this.onLocateError = () => {
            this.setState({
                mobileEditor: {
                    visible: true,
                    options: (this.state.mobileEditor || {}).options || {}
                },
            });
        };
        this.state = {
            located: false,
            moved: false
        };
        this.props.formContext.services.settings.bind(this, props);
    }
    componentDidMount() {
        const { uiSchema } = this.props;
        const { mapOptions = {} } = (0, utils_1.getUiOptions)(uiSchema);
        if (mapOptions.singleton) {
            const { map } = this.props.formContext.services.singletonMap;
            if (map && map.getOptions().locate && map.userLocation) {
                this.onLocate(map.userLocation.latlng, map.userLocation.accuracy);
            }
        }
        this.geocode(this.props);
        this.props.formContext.services.customEvents.add(this.props.idSchema.$id, "locate", this.onLocateEventHandler);
        const { mobileEditor } = (0, utils_1.getUiOptions)(uiSchema);
        if (mobileEditor) {
            this.showMobileEditorMap();
        }
    }
    componentWillUnmount() {
        this.props.formContext.services.customEvents.remove(this.props.idSchema.$id, "locate", this.onLocateEventHandler);
    }
    componentDidUpdate(prevProps) {
        this.geocode(prevProps);
        this.zoomIfExternalEdit(this.props);
        this._lastFormData = this.props.formData;
        if (this._zoomToDataOnNextTick) {
            this.map.zoomToData();
            this._zoomToDataOnNextTick = undefined;
        }
    }
    render() {
        var _a;
        const TitleFieldTemplate = (0, utils_3.getTemplate)("TitleFieldTemplate", this.props.registry, (0, utils_1.getUiOptions)(this.props.uiSchema));
        const { uiSchema, formData } = this.props;
        const { height = 400, emptyHelp, mapOptions = {}, mobileEditor: _mobileEditor, data } = (0, utils_1.getUiOptions)(uiSchema);
        const isEmpty = !formData || !formData.geometries || !formData.geometries.length;
        const _mapOptions = Object.assign(Object.assign(Object.assign({ controls: true, clickBeforeZoomAndPan: true }, mapOptions), (this.state.mapOptions || {})), { locate: {
                on: this.state.locateOn || false,
                onLocationFound: this.onLocate,
                onLocationError: this.onLocateError
            } });
        const isSingleton = _mapOptions.singleton;
        let singletonHasLocate = false;
        let singletonRendered = false;
        if (isSingleton) {
            const { map } = this.props.formContext.services.singletonMap;
            if (map) {
                singletonRendered = true;
                singletonHasLocate = map.getOptions().locate;
            }
        }
        if (mapOptions.createOnLocate && !this.state.mapOptions && (!singletonRendered || singletonHasLocate) && !_mobileEditor) {
            _mapOptions.locate.on = true;
        }
        if (_mobileEditor) {
            _mapOptions.controls = false;
            _mapOptions.viewLocked = true;
            _mapOptions.customControls = [{
                    text: this.props.formContext.translations.ChooseLocation,
                    fn: this.showMobileEditorMap,
                    iconCls: "glyphicon glyphicon-pencil"
                }];
        }
        const { lang, topOffset, bottomOffset } = this.props.formContext;
        const { mobileEditor } = this.state;
        let mobileEditorOptions = (0, utils_1.isObject)(mobileEditor) ? mobileEditor : {};
        if (this.map && this.map.userLocation) {
            mobileEditorOptions.userLocation = this.map.userLocation;
        }
        const extraData = Array.isArray(data) ? data : [data].map((geoData) => ({ geoData }));
        return (React.createElement("div", null,
            React.createElement(TitleFieldTemplate, { title: this.props.schema.title, schema: this.props.schema }),
            React.createElement(components_1.Affix, { topOffset, bottomOffset },
                React.createElement("div", { style: { height } },
                    React.createElement(MapArrayField_1.MapComponent, Object.assign({}, _mapOptions, { ref: this.setMapRef, draw: this.getDrawOptions(this.props), data: extraData, lang: lang, zoomToData: { paddingInMeters: 200 }, panel: emptyHelp && isEmpty ? { panelTextContent: emptyHelp } : undefined, formContext: this.props.formContext, onOptionsChanged: this.onOptionsChanged })),
                    this.map && this.map.container && (0, react_dom_1.createPortal)(this.renderBlocker(), this.map.container))),
            this.state.mapRendered && mobileEditor && mobileEditor.visible &&
                React.createElement(MobileEditorMap, Object.assign({}, mobileEditorOptions, { options: mobileEditor.options, onChange: this.onMobileEditorChange, onClose: this.onHideMobileEditorMap, map: this.map, formContext: this.props.formContext, geometry: this.getMobileGeometry(), moved: this.state.moved, setMoved: this.setMoved, defaultLocation: !this.props.formData && !((_a = this.map) === null || _a === void 0 ? void 0 : _a.userLocation) }))));
    }
    renderBlocker() {
        const { blockBeforeLocation } = (0, utils_1.getUiOptions)(this.props.uiSchema);
        const geometry = this.getGeometry(this.props);
        if (blockBeforeLocation && !this.state.located && !geometry) {
            return (React.createElement(React.Fragment, null,
                React.createElement("div", { className: "blocker" }),
                React.createElement("div", { className: "blocker-content" },
                    React.createElement("span", null,
                        this.props.formContext.translations.SearchingForLocation,
                        "..."),
                    React.createElement(react_spinner_1.default, null))));
        }
    }
}
MapField.propTypes = {
    uiSchema: PropTypes.shape({
        "ui:options": PropTypes.shape({
            mapOptions: PropTypes.object,
            height: PropTypes.number,
            emptyHelp: PropTypes.string,
            geometryCollection: PropTypes.bool,
            coordinateFields: PropTypes.shape({
                latitude: PropTypes.string.isRequired,
                longitude: PropTypes.string.isRequired
            })
        })
    }),
    schema: PropTypes.shape({
        type: PropTypes.oneOf(["object"])
    }).isRequired,
    formData: PropTypes.object
};
exports.default = MapField;
class MobileEditorMap extends React.Component {
    constructor(props) {
        super(props);
        this.DEFAULT_RADIUS_PIXELS = 100;
        this.setMobileEditorMapRef = (mapComponent) => {
            if (mapComponent && mapComponent.refs && mapComponent.refs.map) {
                this.map = mapComponent.refs.map.map;
            }
            this.setState({ mapRendered: true });
        };
        this.setOkButtonRef = (elem) => {
            this.okButtonElem = (0, react_dom_1.findDOMNode)(elem);
        };
        this.onChange = () => {
            if (this.marker) {
                const { lat, lng } = this.marker.getLatLng();
                this.map.map.setView({ lng, lat }, 12);
                const markerGeometry = {
                    type: "Point",
                    coordinates: [lng, lat]
                };
                this.props.onChange(markerGeometry);
            }
            this.onClose();
        };
        this.handleMapClick = (e) => {
            if (!this.props.moved) {
                this.props.setMoved(true);
            }
            this.setMarkerLatLng(e.latlng);
        };
        this.setMarkerLatLng = (latlng) => {
            if (this.marker) {
                this.marker.setLatLng(latlng);
            }
            else {
                this.marker = L.marker(latlng, { icon: this.map._createIcon(), draggable: true }).addTo(this.map.map);
            }
        };
        this.invisibleStyle = () => {
            return {
                opacity: 0,
                fillOpacity: 0
            };
        };
        this.onKeyDown = ({ key }) => {
            if (key === "Escape") {
                this.onClose();
            }
        };
        this.activateLocate = () => {
            this.handleLocateToggle(true);
        };
        this.handleLocateToggle = (bool) => {
            this.setState({ locateActive: bool, hasLocated: false });
            this.map.setOption("locate", { on: bool });
        };
        this.onLocate = (latlng) => {
            if (!latlng || this.state.hasLocated) {
                return;
            }
            this.map.map.setView({ lng: latlng.lng, lat: latlng.lat }, 12);
            this.setMarkerLatLng(latlng);
            if (!this.state.hasLocated) {
                this.setState({ hasLocated: true });
            }
            if (!this.props.moved) {
                this.props.setMoved(true);
            }
        };
        this.onClose = () => {
            this.props.onClose(this.map.getOptions());
        };
        const { geometry } = this.props;
        this.state = {
            geometry: [{ geoData: geometry }],
            locateActive: false,
            hasLocated: false
        };
    }
    componentDidMount() {
        this.mounted = true;
        this.okButtonElem.focus();
        this.map.setLocateOff();
        if (this.props.geometry) {
            const [lng, lat] = this.props.geometry.coordinates;
            this.setMarkerLatLng({ lng, lat });
            if (this.props.defaultLocation) {
                this.map.map.setView({ lng, lat }, 4);
            }
            else {
                this.map.map.setView({ lng, lat }, 12);
                this.props.setMoved(true);
            }
            this.marker.on("dragend", () => {
                if (!this.props.moved) {
                    this.props.setMoved(true);
                }
            });
        }
        this.map.map.on("click", this.handleMapClick);
    }
    componentWillUnmount() {
        this.mounted = false;
        if (this.marker) {
            this.marker.remove();
        }
        this.map.map.off("click", this.handleMapClick);
    }
    render() {
        let _a = this.props.map.getOptions(), { rootElem, customControls, draw, data, zoomToData, zoom, locate } = _a, options = __rest(_a, ["rootElem", "customControls", "draw", "data", "zoomToData", "zoom", "locate"]);
        const { userLocation } = this.props;
        const { translations } = this.props.formContext;
        const mapComponentProps = Object.assign(Object.assign(Object.assign({}, options), (this.props.options || {})), { locate: {
                on: this.state.locateActive,
                userLocation,
                onLocationFound: this.onLocate,
                panOnFound: false
            }, singleton: true, clickBeforeZoomAndPan: false, viewLocked: false, controls: { draw: false }, ref: this.setMobileEditorMapRef, formContext: this.props.formContext, panel: {
                panelTextContent: this.props.formContext.translations.MobileMapInstructions
            } });
        return (React.createElement(components_1.Fullscreen, { onKeyDown: this.onKeyDown, tabIndex: -1, ref: this.setContainerRef, formContext: this.props.formContext },
            React.createElement(MapArrayField_1.MapComponent, Object.assign({}, mapComponentProps, { onLocateToggle: this.handleLocateToggle })),
            React.createElement("div", { className: "floating-buttons-container" },
                React.createElement(components_1.Button, { block: true, onClick: this.onChange, className: "choose-location-button", variant: "primary", ref: this.setOkButtonRef, disabled: !this.props.moved }, translations.ChooseThisLocation),
                React.createElement(components_1.Button, { block: true, onClick: this.activateLocate, className: "locate-me-button", variant: "default", disabled: this.state.locateActive }, translations.LocateMe))));
    }
}
