import { IconLookup, faCircleXmark, faGear, faPause, faPlay, faRotate } from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { Checkbox, FormControlLabel, TextField } from '@mui/material';
import * as turf from '@turf/turf';
import { IconButton, Size, Type } from '@dash/components';
import * as d3 from 'd3';
import { Feature, FeatureCollection, GeoJsonProperties, Geometry, LineString, Position } from 'geojson';
import mapboxgl, { LngLatBoundsLike, MapLayerMouseEvent } from 'mapbox-gl';
import { IReactionDisposer, ObservableMap, action, autorun, computed, makeObservable, observable, runInAction } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
import { CirclePicker, ColorResult } from 'react-color';
import { Layer, MapProvider, MapRef, Map as MapboxMap, Marker, Source, ViewState, ViewStateChangeEvent } from 'react-map-gl';
import { MarkerEvent } from 'react-map-gl/dist/esm/types';
import { ClientUtils, setupMoveUpEvents } from '../../../../ClientUtils';
import { emptyFunction } from '../../../../Utils';
import { Doc, DocListCast, Field, LinkedTo, Opt } from '../../../../fields/Doc';
import { DocCast, NumCast, StrCast, toList } from '../../../../fields/Types';
import { DocUtils } from '../../../documents/DocUtils';
import { DocumentType } from '../../../documents/DocumentTypes';
import { Docs } from '../../../documents/Documents';
import { DragManager } from '../../../util/DragManager';
import { UndoManager, undoable } from '../../../util/UndoManager';
import { ViewBoxAnnotatableComponent } from '../../DocComponent';
import { PinDocView, PinProps } from '../../PinFuncs';
import { SidebarAnnos } from '../../SidebarAnnos';
import { MarqueeOptionsMenu } from '../../collections/collectionFreeForm';
import { Colors } from '../../global/globalEnums';
import { DocumentView } from '../DocumentView';
import { FieldView, FieldViewProps } from '../FieldView';
import { FocusViewOptions } from '../FocusViewOptions';
import { fastSpeedIcon, mediumSpeedIcon, slowSpeedIcon } from './AnimationSpeedIcons';
import { AnimationSpeed, AnimationStatus, AnimationUtility } from './AnimationUtility';
import { MapAnchorMenu } from './MapAnchorMenu';
import './MapBox.scss';
import { MapboxApiUtility, TransportationType } from './MapboxApiUtility';
import { MarkerIcons } from './MarkerIcons';
// import { GeocoderControl } from './GeocoderControl';

// amongus
/**
 * MapBox architecture:
 * Main component: MapBox.tsx
 * Supporting Components: SidebarAnnos, CollectionStackingView
 *
 * MapBox is a node that extends the ViewBoxAnnotatableComponent. Similar to PDFBox and WebBox, it supports interaction between sidebar content and document content.
 * The main body of MapBox uses Google Maps API to allow location retrieval, adding map markers, pan and zoom, and open street view.
 * Dash Document architecture is integrated with Maps API: When drag and dropping documents with ExifData (gps Latitude and Longitude information) available,
 * sidebarAddDocument function checks if the document contains lat & lng information, if it does, then the document is added to both the sidebar and the infowindow (a pop up corresponding to a map marker--pin on map).
 * The lat and lng field of the document is filled when importing (spec see ConvertDMSToDD method and processFileUpload method in Documents.ts).
 * A map marker is considered a document that contains a collection with stacking view of documents, it has a lat, lng location, which is passed to Maps API's custom marker (red pin) to be rendered on the google maps
 */

const MAPBOX_ACCESS_TOKEN = 'pk.eyJ1IjoiemF1bHRhdmFuZ2FyIiwiYSI6ImNscHgwNDd1MDA3MXIydm92ODdianp6cGYifQ.WFAqbhwxtMHOWSPtu0l2uQ';

type PopupInfo = {
    longitude: number;
    latitude: number;
    title: string;
    description: string;
};

@observer
export class MapBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
    public static LayoutString(fieldKey: string) {
        return FieldView.LayoutString(MapBox, fieldKey);
    }
    private _unmounting = false;
    private _sidebarRef = React.createRef<SidebarAnnos>();
    private _ref: React.RefObject<HTMLDivElement> = React.createRef();
    private _mapRef: React.RefObject<MapRef> = React.createRef();
    private _disposers: { [key: string]: IReactionDisposer } = {};

    constructor(props: FieldViewProps) {
        super(props);
        makeObservable(this);
    }

    @observable _featuresFromGeocodeResults: any[] = [];
    @observable _savedAnnotations = new ObservableMap<number, HTMLDivElement[]>();
    @observable _selectedPinOrRoute: Doc | undefined = undefined; // The pin that is selected
    @observable _mapReady = false;
    @observable _isAnimating: boolean = false;
    @observable _routeToAnimate: Doc | undefined = undefined;
    @observable _animationPhase: number = 0;
    @observable _finishedFlyTo: boolean = false;
    @observable _frameId: number | null = null;
    @observable _animationUtility: AnimationUtility | null = null;
    @observable _settingsOpen: boolean = false;
    @observable _mapStyle: string = 'mapbox://styles/mapbox/standard';
    @observable _showTerrain: boolean = true;
    @observable _currentPopup: PopupInfo | undefined = undefined;
    @observable _isStreetViewAnimation: boolean = false;
    @observable _animationSpeed: AnimationSpeed = AnimationSpeed.MEDIUM;
    @observable _animationLineColor: string = '#ffff00';
    @observable _temporaryRouteSource: FeatureCollection = { type: 'FeatureCollection', features: [] };
    @observable _dynamicRouteFeature: Feature<Geometry, GeoJsonProperties> = {
        type: 'Feature',
        properties: {},
        geometry: { type: 'LineString', coordinates: [] },
    };

    @observable path: turf.helpers.Feature<turf.helpers.LineString, turf.helpers.Properties> = {
        type: 'Feature',
        geometry: { type: 'LineString', coordinates: [] },
        properties: {},
    };

    // this list contains pushpins and configs
    @computed get allAnnotations() { return DocListCast(this.dataDoc[this.annotationKey]); } // prettier-ignore
    @computed get allSidebarDocs() { return DocListCast(this.dataDoc[this.SidebarKey]); } // prettier-ignore
    @computed get allPushpins() { return this.allAnnotations.filter(anno => anno.type === DocumentType.PUSHPIN); } // prettier-ignore
    @computed get allRoutes() { return this.allAnnotations.filter(anno => anno.type === DocumentType.MAPROUTE); } // prettier-ignore
    @computed get SidebarShown() { return !!this.layoutDoc._layout_showSidebar; } // prettier-ignore
    @computed get sidebarWidthPercent() { return StrCast(this.layoutDoc._layout_sidebarWidthPercent, '0%'); } // prettier-ignore
    @computed get SidebarKey() {  return this.fieldKey + '_sidebar'; } // prettier-ignore
    @computed get sidebarColor() {
        return StrCast(this.layoutDoc.sidebar_color, StrCast(this.layoutDoc[this._props.fieldKey + '_backgroundColor'], '#e4e4e4'));
    }
    @computed get updatedRouteCoordinates(): Feature<Geometry, GeoJsonProperties> {
        if (this._routeToAnimate?.routeCoordinates) {
            const originalCoordinates: Position[] = JSON.parse(StrCast(this._routeToAnimate.routeCoordinates));
            // const index = Math.floor(this.animationPhase * originalCoordinates.length);
            const index = this._animationPhase * (originalCoordinates.length - 1); // Calculate the fractional index
            console.log('Animation phase', this._animationPhase);
            const startIndex = Math.floor(index);
            const endIndex = Math.ceil(index);
            let feature: Feature<Geometry, GeoJsonProperties>;

            let geometry: LineString;
            if (startIndex === endIndex) {
                // AnimationPhase is at a whole number (no interpolation needed)
                const coordinates = [originalCoordinates[startIndex]];
                geometry = {
                    type: 'LineString',
                    coordinates,
                };
                feature = {
                    type: 'Feature',
                    properties: {
                        routeTitle: StrCast(this._routeToAnimate.title),
                    },
                    geometry: geometry,
                };
            } else {
                // Interpolate between two coordinates
                const startCoord = originalCoordinates[startIndex];
                const endCoord = originalCoordinates[endIndex];
                const fraction = index - startIndex;

                const interpolator = d3.interpolateArray(startCoord, endCoord);
                const interpolatedCoord = interpolator(fraction);
                const coordinates = originalCoordinates.slice(0, startIndex + 1).concat([interpolatedCoord]);

                geometry = {
                    type: 'LineString',
                    coordinates,
                };
                feature = {
                    type: 'Feature',
                    properties: {
                        routeTitle: StrCast(this._routeToAnimate.title),
                    },
                    geometry: geometry,
                };
            }

            autorun(() => {
                const animationUtil = this._animationUtility;
                const concattedCoordinates = geometry.coordinates.concat(originalCoordinates.slice(endIndex));
                const newFeature: Feature<LineString, turf.Properties> = {
                    type: 'Feature',
                    properties: {},
                    geometry: {
                        type: 'LineString',
                        coordinates: concattedCoordinates,
                    },
                };
                if (animationUtil) {
                    animationUtil.setPath(newFeature);
                }
            });
            return feature;
        }
        console.log('ERROR');
        return {
            type: 'Feature',
            properties: {},
            geometry: {
                type: 'LineString',
                coordinates: [],
            },
        };
    }
    @computed get selectedRouteCoordinates(): Position[] {
        return !this._routeToAnimate?.routeCoordinates ? [] : JSON.parse(StrCast(this._routeToAnimate.routeCoordinates));
    }

    @computed get allRoutesGeoJson(): FeatureCollection {
        const features: Feature<Geometry, GeoJsonProperties>[] = this.allRoutes.map((routeDoc: Doc) => {
            console.log('Route coords: ', routeDoc.routeCoordinates);
            const geometry: LineString = {
                type: 'LineString',
                coordinates: JSON.parse(StrCast(routeDoc.routeCoordinates)),
            };
            return {
                type: 'Feature',
                properties: {
                    routeTitle: routeDoc.title,
                },
                geometry: geometry,
            };
        });

        return {
            type: 'FeatureCollection',
            features: features,
        };
    }

    componentDidMount() {
        this._unmounting = false;
        this._props.setContentViewBox?.(this);
    }

    componentWillUnmount() {
        this._unmounting = true;
        this.deselectPinOrRoute();
        Object.keys(this._disposers).forEach(key => this._disposers[key]?.());
    }

    /**
     * Called when dragging documents into map sidebar or directly into infowindow; to create a map marker, ref to MapMarkerDocument in Documents.ts
     * @param docs
     * @param sidebarKey
     * @returns
     */
    sidebarAddDocument = (docs: Doc | Doc[], sidebarKey?: string) => {
        if (!this.layoutDoc._layout_showSidebar) this.toggleSidebar();
        toList(docs).forEach(doc => {
            let existingPin = this.allPushpins.find(pin => pin.latitude === doc.latitude && pin.longitude === doc.longitude) ?? this._selectedPinOrRoute;
            if (doc.latitude !== undefined && doc.longitude !== undefined && !existingPin) {
                existingPin = this.createPushpin(NumCast(doc.latitude), NumCast(doc.longitude), StrCast(doc.map));
            }
            if (existingPin) {
                setTimeout(() => {
                    // we use a timeout in case this is called from the sidebar which may have just added a link that hasn't made its way into th elink manager yet
                    if (!Doc.Links(doc).some(link => DocCast(link.link_anchor_1)?.mapPin === existingPin || DocCast(link.link_anchor_2)?.mapPin === existingPin)) {
                        const anchor = this.getAnchor(true, undefined, existingPin);
                        anchor && DocUtils.MakeLink(anchor, doc, { link_relationship: 'link to map location' });
                        doc.latitude = existingPin?.latitude;
                        doc.longitude = existingPin?.longitude;
                    }
                });
            }
        }); // add to annotation list

        return this.addDocument(docs, sidebarKey); // add to sidebar list
    };

    removeMapDocument = (doc: Doc | Doc[], annotationKey?: string) => {
        this.allAnnotations
            .filter(anno => toList(doc).includes(DocCast(anno.mapPin)))
            .forEach(anno => {
                anno.mapPin = undefined;
            });
        return this.removeDocument(doc, annotationKey, undefined);
    };

    /**
     * Removing documents from the sidebar
     * @param doc
     * @param sidebarKey
     * @returns
     */
    sidebarRemoveDocument = (doc: Doc | Doc[], sidebarKey?: string) => this.removeMapDocument(doc, sidebarKey);

    /**
     * Toggle sidebar onclick the tiny comment button on the top right corner
     * @param e
     */
    sidebarBtnDown = (e: React.PointerEvent) => {
        setupMoveUpEvents(
            this,
            e,
            (moveEv, down, delta) =>
                runInAction(() => {
                    const localDelta = this._props
                        .ScreenToLocalTransform()
                        .scale(this._props.NativeDimScaling?.() || 1)
                        .transformDirection(delta[0], delta[1]);
                    const fullWidth = NumCast(this.layoutDoc._width);
                    const mapWidth = fullWidth - this.sidebarWidth();
                    if (this.sidebarWidth() + localDelta[0] > 0) {
                        this.layoutDoc._layout_showSidebar = true;
                        this.layoutDoc._width = fullWidth + localDelta[0];
                        this.layoutDoc._layout_sidebarWidthPercent = ((100 * (this.sidebarWidth() + localDelta[0])) / (fullWidth + localDelta[0])).toString() + '%';
                    } else {
                        this.layoutDoc._layout_showSidebar = false;
                        this.layoutDoc._width = mapWidth;
                        this.layoutDoc._layout_sidebarWidthPercent = '0%';
                    }
                    return false;
                }),
            emptyFunction,
            () => UndoManager.RunInBatch(this.toggleSidebar, 'toggle sidebar map')
        );
    };
    sidebarWidth = () => (Number(this.sidebarWidthPercent.substring(0, this.sidebarWidthPercent.length - 1)) / 100) * this._props.PanelWidth();

    /**
     * Handles toggle of sidebar on click the little comment button
     */
    @computed get sidebarHandle() {
        return (
            <div
                className="mapBox-overlayButton-sidebar"
                key="sidebar"
                title="Toggle Sidebar"
                style={{
                    display: !this._props.isContentActive() ? 'none' : undefined,
                    top: StrCast(this.Document._layout_showTitle) === 'title' ? 20 : 5,
                    backgroundColor: this.SidebarShown ? Colors.MEDIUM_BLUE : Colors.BLACK,
                }}
                onPointerDown={this.sidebarBtnDown}>
                <FontAwesomeIcon style={{ color: Colors.WHITE }} icon="comment-alt" size="sm" />
            </div>
        );
    }

    // TODO: Adding highlight box layer to Maps
    @action
    toggleSidebar = () => {
        const prevWidth = this.sidebarWidth();
        this.layoutDoc._layout_showSidebar = (this.layoutDoc._layout_sidebarWidthPercent = StrCast(this.layoutDoc._layout_sidebarWidthPercent, '0%') === '0%' ? `${(100 * 0.2) / 1.2}%` : '0%') !== '0%';
        this.layoutDoc._width = this.layoutDoc._layout_showSidebar ? NumCast(this.layoutDoc._width) * 1.2 : Math.max(20, NumCast(this.layoutDoc._width) - prevWidth);
    };

    startAnchorDrag = (e: PointerEvent, ele: HTMLElement) => {
        e.preventDefault();
        e.stopPropagation();

        const sourceAnchorCreator = action(() => {
            const note = this.getAnchor(true);
            if (note && this._selectedPinOrRoute) {
                note.latitude = this._selectedPinOrRoute.latitude;
                note.longitude = this._selectedPinOrRoute.longitude;
                note.map = this._selectedPinOrRoute.map;
            }
            return note as Doc;
        });

        const targetCreator = (annotationOn: Doc | undefined) => {
            const target = DocUtils.GetNewTextDoc('Note linked to ' + this.Document.title, 0, 0, 100, 100, annotationOn, 'yellow');
            Doc.SetSelectOnLoad(target);
            return target;
        };
        const docView = this.DocumentView?.();
        docView &&
            DragManager.StartAnchorAnnoDrag([ele], new DragManager.AnchorAnnoDragData(docView, sourceAnchorCreator, targetCreator), e.pageX, e.pageY, {
                dragComplete: dragEv => {
                    if (!dragEv.aborted && dragEv.annoDragData && dragEv.annoDragData.linkSourceDoc && dragEv.annoDragData.dropDocument && dragEv.linkDocument) {
                        dragEv.annoDragData.linkSourceDoc.followLinkToggle = dragEv.annoDragData.dropDocument.annotationOn === this.Document;
                        dragEv.annoDragData.linkSourceDoc.followLinkZoom = false;
                    }
                },
            });
    };

    createNoteAnnotation = () => {
        const createFunc = undoable(
            action(() => {
                const note = this._sidebarRef.current?.anchorMenuClick(this.getAnchor(true), ['latitude', 'longitude', LinkedTo]);
                if (note && this._selectedPinOrRoute) {
                    note.latitude = this._selectedPinOrRoute.latitude;
                    note.longitude = this._selectedPinOrRoute.longitude;
                    note.map = this._selectedPinOrRoute.map;
                }
            }),
            'create note annotation'
        );
        if (!this.layoutDoc.layout_showSidebar) {
            this.toggleSidebar();
            setTimeout(createFunc);
        } else createFunc();
    };
    sidebarDown = (e: React.PointerEvent) => {
        setupMoveUpEvents(this, e, this.sidebarMove, emptyFunction, () => setTimeout(this.toggleSidebar), true);
    };
    sidebarMove = (e: PointerEvent) => {
        const bounds = this._ref.current!.getBoundingClientRect();
        this.layoutDoc._layout_sidebarWidthPercent = '' + 100 * Math.max(0, 1 - (e.clientX - bounds.left) / bounds.width) + '%';
        this.layoutDoc._layout_showSidebar = this.layoutDoc._layout_sidebarWidthPercent !== '0%';
        e.preventDefault();
        return false;
    };

    pointerEvents = () => (this._props.isContentActive() && !MarqueeOptionsMenu.Instance.isShown() ? 'all' : 'none');
    panelWidth = () => this._props.PanelWidth() / (this._props.NativeDimScaling?.() || 1) - this.sidebarWidth();
    panelHeight = () => this._props.PanelHeight() / (this._props.NativeDimScaling?.() || 1);
    scrollXf = () => this.ScreenToLocalBoxXf().translate(0, NumCast(this.layoutDoc._layout_scrollTop));
    transparentFilter = () => [...this._props.childFilters(), ClientUtils.TransparentBackgroundFilter];
    opaqueFilter = () => [...this._props.childFilters(), ClientUtils.OpaqueBackgroundFilter];
    infoWidth = () => this._props.PanelWidth() / 5;
    infoHeight = () => this._props.PanelHeight() / 5;
    anchorMenuClick = () => this._sidebarRef.current?.anchorMenuClick;
    savedAnnotations = () => this._savedAnnotations;

    @action
    deselectPinOrRoute = () => {
        if (this._selectedPinOrRoute) {
            // // Removes filter
            // Doc.setDocFilter(this.Document, 'latitude', this.selectedPin.latitude, 'remove');
            // Doc.setDocFilter(this.Document, 'longitude', this.selectedPin.longitude, 'remove');
            // Doc.setDocFilter(this.Document, LinkedTo, `mapPin=${Field.toScriptString(DocCast(this.selectedPin))}`, 'remove');
            // const temp = this.selectedPin;
            // if (!this._unmounting) {
            //     this._bingMap.current.entities.remove(this.map_docToPinMap.get(temp));
            // }
            // const newpin = new this.MicrosoftMaps.Pushpin(new this.MicrosoftMaps.Location(temp.latitude, temp.longitude));
            // this.MicrosoftMaps.Events.addHandler(newpin, 'click', (e: any) => this.pushpinClicked(temp as Doc));
            // if (!this._unmounting) {
            //     this._bingMap.current.entities.push(newpin);
            // }
            // this.map_docToPinMap.set(temp, newpin);
            // this.selectedPin = undefined;
            // this.bingSearchBarContents = this.Document.map;
        }
    };

    getView = (doc: Doc, options: FocusViewOptions) => {
        if (this._sidebarRef?.current?.makeDocUnfiltered(doc) && !this.SidebarShown) {
            this.toggleSidebar();
            options.didMove = true;
        }
        return new Promise<Opt<DocumentView>>(res => {
            DocumentView.addViewRenderedCb(doc, dv => res(dv));
        });
    };

    /*
     * Returns doc w/ relevant info
     */
    getAnchor = (addAsAnnotation: boolean, pinProps?: PinProps, existingPin?: Doc) => {
        /// this should use SELECTED pushpin for lat/long  if there is a selection, otherwise CENTER
        const anchor = Docs.Create.ConfigDocument({
            title: 'MapAnchor:' + this.Document.title,
            text: (StrCast(this._selectedPinOrRoute?.map) || StrCast(this.Document.map) || 'map location') as any,
            config_latitude: NumCast((existingPin ?? this._selectedPinOrRoute)?.latitude ?? this.dataDoc.latitude),
            config_longitude: NumCast((existingPin ?? this._selectedPinOrRoute)?.longitude ?? this.dataDoc.longitude),
            config_map_zoom: NumCast(this.dataDoc.map_zoom),
            // config_map_type: StrCast(this.dataDoc.map_type),
            config_map: StrCast((existingPin ?? this._selectedPinOrRoute)?.map) || StrCast(this.dataDoc.map),
            layout_unrendered: true,
            mapPin: existingPin ?? this._selectedPinOrRoute,
            annotationOn: this.Document,
        });
        if (anchor) {
            if (!addAsAnnotation) anchor.backgroundColor = 'transparent';
            addAsAnnotation && this.addDocument(anchor);
            PinDocView(anchor, { pinDocLayout: pinProps?.pinDocLayout, pinData: { ...(pinProps?.pinData ?? {}), map: true } }, this.Document);
            return anchor;
        }
        return this.Document;
    };

    map_docToPinMap = new Map<Doc, any>();
    map_pinHighlighted = new Map<Doc, boolean>();

    /*
     *   Input: pin doc
     *   Removes pin from annotations
     */
    @action
    removePushpinOrRoute = (pinOrRouteDoc: Doc) => this.removeMapDocument(pinOrRouteDoc, this.annotationKey);

    @action
    deleteSelectedPinOrRoute = undoable(() => {
        console.log('deleting');
        if (this._selectedPinOrRoute) {
            // Removes filter
            Doc.setDocFilter(this.Document, 'latitude', NumCast(this._selectedPinOrRoute.latitude), 'remove');
            Doc.setDocFilter(this.Document, 'longitude', NumCast(this._selectedPinOrRoute.longitude), 'remove');
            Doc.setDocFilter(this.Document, LinkedTo, `mapPin=${Field.toScriptString(DocCast(this._selectedPinOrRoute))}`, 'remove');

            this.removePushpinOrRoute(this._selectedPinOrRoute);
        }
        MapAnchorMenu.Instance.fadeOut(true);
        document.removeEventListener('pointerdown', this.tryHideMapAnchorMenu, true);
    }, 'delete pin');

    tryHideMapAnchorMenu = (e: PointerEvent) => {
        let target = document.elementFromPoint(e.x, e.y);
        while (target) {
            if (target.id === 'route-destination-searcher-listbox') return;
            if (target === MapAnchorMenu.top.current) return;
            target = target.parentElement;
        }
        e.stopPropagation();
        e.preventDefault();
        MapAnchorMenu.Instance.fadeOut(true);
        runInAction(() => {
            this._temporaryRouteSource = {
                type: 'FeatureCollection',
                features: [],
            };
        });

        document.removeEventListener('pointerdown', this.tryHideMapAnchorMenu, true);
    };

    @action
    centerOnSelectedPin = () => {
        if (this._selectedPinOrRoute) {
            this._mapRef.current?.flyTo({
                center: [NumCast(this._selectedPinOrRoute.longitude), NumCast(this._selectedPinOrRoute.latitude)],
            });
        }
        // if (this.selectedPin) {
        //     this.dataDoc.latitude = this.selectedPin.latitude;
        //     this.dataDoc.longitude = this.selectedPin.longitude;
        //     this.dataDoc.map = this.selectedPin.map ?? '';
        //     this.bingSearchBarContents = this.selectedPin.map;
        // }
        MapAnchorMenu.Instance.fadeOut(true);
        document.removeEventListener('pointerdown', this.tryHideMapAnchorMenu);
    };

    // eslint-disable-next-line @typescript-eslint/no-unused-vars
    recolorPin = (pin: Doc, color?: string) => {
        // this._bingMap.current.entities.remove(this.map_docToPinMap.get(pin));
        // this.map_docToPinMap.delete(pin);
        // const newpin = new this.MicrosoftMaps.Pushpin(new this.MicrosoftMaps.Location(pin.latitude, pin.longitude), color ? { color } : {});
        // this.MicrosoftMaps.Events.addHandler(newpin, 'click', (e: any) => this.pushpinClicked(pin));
        // this._bingMap.current.entities.push(newpin);
        // this.map_docToPinMap.set(pin, newpin);
    };

    // incrementer: number = 0;
    /*
     * Creates Pushpin doc and adds it to the list of annotations
     */
    @action
    createPushpin = undoable((latitude: number, longitude: number, location?: string, wikiData?: string) => {
        // Stores the pushpin as a MapMarkerDocument
        const pushpin = Docs.Create.PushpinDocument(
            NumCast(latitude),
            NumCast(longitude),
            false,
            [],
            {
                title: location ?? `lat=${NumCast(latitude)},lng=${NumCast(longitude)}`,
                map: location,
                description: '',
                wikiData: wikiData,
                markerType: 'MAP_PIN',
                markerColor: '#ff5722',
            }
            // { title: map ?? `lat=${latitude},lng=${longitude}`, map: map },
            // ,'pushpinIDamongus'+ this.incrementer++
        );
        this.addDocument(pushpin, this.annotationKey);
        console.log(pushpin);
        return pushpin;

        // mapMarker.infoWindowOpen = true;
    }, 'createpin');

    @action
    createMapRoute = undoable((coordinates: Position[], originName: string, destination: any, createPinForDestination: boolean) => {
        if (originName !== destination.place_name) {
            const mapRoute = Docs.Create.MapRouteDocument(false, [], { title: `${originName} --> ${destination.place_name}`, routeCoordinates: JSON.stringify(coordinates) });
            this.addDocument(mapRoute, this.annotationKey);
            if (createPinForDestination) {
                this.createPushpin(destination.center[1], destination.center[0], destination.place_name);
            }
            this._temporaryRouteSource = {
                type: 'FeatureCollection',
                features: [],
            };
            MapAnchorMenu.Instance.fadeOut(true);
            return mapRoute;
        }
        return undefined;
        // TODO: Display error that can't create route to same location
    }, 'createmaproute');

    @action
    searchbarKeyDown = (e: any) => {
        if (e.key === 'Enter' && this._featuresFromGeocodeResults) {
            const center = this._featuresFromGeocodeResults[0]?.center;
            this._featuresFromGeocodeResults = [];
            setTimeout(() => center && this._mapRef.current?.flyTo({ center }));
        }
    };

    @action
    addMarkerForFeature = (feature: any) => {
        const location = feature.place_name;
        if (feature.center) {
            const longitude = feature.center[0];
            const latitude = feature.center[1];
            const wikiData = feature.properties?.wikiData;

            this.createPushpin(latitude, longitude, location, wikiData);

            if (this._mapRef.current) {
                this._mapRef.current.flyTo({
                    center: feature.center,
                });
            }
            this._featuresFromGeocodeResults = [];
        } else {
            // TODO: handle error
        }
    };

    /**
     * Makes a forward geocoding API call to Mapbox to retrieve locations based on the search input
     * @param searchText the search input (presumably a location)
     */
    handleSearchChange = async (searchText: string) => {
        const features = await MapboxApiUtility.forwardGeocodeForFeatures(searchText);
        if (features && !this._isAnimating) {
            runInAction(() => {
                this._settingsOpen = false;
                this._featuresFromGeocodeResults = features;
                this._routeToAnimate = undefined;
            });
        }
        // try {
        //     const url = MAPBOX_FORWARD_GEOCODE_BASE_URL + encodeURI(searchText) +'.json' +`?access_token=${MAPBOX_ACCESS_TOKEN}`;
        //     const response = await fetch(url);
        //     const data = await response.json();
        //     runInAction(() => {
        //         this.featuresFromGeocodeResults = data.features;
        //     })
        // } catch (error: any){
        //     // TODO: handle error in better way
        //     console.log(error);
        // }
    };
    // @action
    // debouncedCall = React.useCallback(debounce(this.debouncedOnSearchBarChange, 300), []);

    @action
    handleMapClick = (e: MapLayerMouseEvent) => {
        this._featuresFromGeocodeResults = [];
        this._settingsOpen = false;
        if (this._mapRef.current) {
            const features = this._mapRef.current.queryRenderedFeatures(e.point, {
                layers: ['map-routes-layer'],
            });

            console.error(features);
            if (features && features.length > 0 && features[0].properties && features[0].geometry) {
                const { routeTitle } = features[0].properties;
                const routeDoc: Doc | undefined = this.allRoutes.find(rtDoc => rtDoc.title === routeTitle);
                this.deselectPinOrRoute(); // TODO: Also deselect route if selected
                if (routeDoc) {
                    this._selectedPinOrRoute = routeDoc;
                    Doc.setDocFilter(this.Document, LinkedTo, `mapRoute=${Field.toScriptString(this._selectedPinOrRoute)}`, 'check');

                    // TODO: Recolor route

                    MapAnchorMenu.Instance.Delete = this.deleteSelectedPinOrRoute;
                    MapAnchorMenu.Instance.Center = this.centerOnSelectedPin;
                    MapAnchorMenu.Instance.OnClick = this.createNoteAnnotation;
                    MapAnchorMenu.Instance.StartDrag = this.startAnchorDrag;

                    MapAnchorMenu.Instance.Reset();

                    MapAnchorMenu.Instance.setRouteDoc(routeDoc);

                    // TODO: Subject to change
                    MapAnchorMenu.Instance.setAllMapboxPins(this.allAnnotations.filter(anno => !anno.layout_unrendered));

                    MapAnchorMenu.Instance.DisplayRoute = this.displayRoute;
                    MapAnchorMenu.Instance.AddNewRouteToMap = this.createMapRoute;
                    MapAnchorMenu.Instance.CreatePin = this.addMarkerForFeature;
                    MapAnchorMenu.Instance.OpenAnimationPanel = this.openAnimationPanel;

                    // this.selectedRouteCoordinates = geometry.coordinates;

                    MapAnchorMenu.Instance.setMenuType('route');

                    MapAnchorMenu.Instance.jumpTo(e.originalEvent.clientX, e.originalEvent.clientY, true);

                    document.addEventListener('pointerdown', this.tryHideMapAnchorMenu, true);
                }
            }
        }
    };

    /**
     * Makes a reverse geocoding API call to retrieve features corresponding to a map click (based on longitude
     * and latitude). Sets the search results accordingly.
     * @param e
     */
    handleMapDblClick = async (e: MapLayerMouseEvent) => {
        e.preventDefault();
        const { lngLat } = e;
        const longitude: number = lngLat.lng;
        const latitude: number = lngLat.lat;

        const features = await MapboxApiUtility.reverseGeocodeForFeatures(longitude, latitude);
        if (features) {
            runInAction(() => {
                this._featuresFromGeocodeResults = features;
            });
        }

        // // REVERSE GEOCODE TO GET LOCATION DETAILS
        // try {
        //     const url = MAPBOX_REVERSE_GEOCODE_BASE_URL + encodeURI(longitude.toString() + "," + latitude.toString()) + '.json' +
        //         `?access_token=${MAPBOX_ACCESS_TOKEN}`;
        //     const response = await fetch(url);
        //     const data = await response.json();
        //     console.log("REV GEOCODE DATA: ", data);
        //     runInAction(() => {
        //         this.featuresFromGeocodeResults = data.features;
        //     })
        // } catch (error: any){
        //     // TODO: handle error in better way
        //     console.log(error);
        // }
    };

    @action
    handleMarkerClick = (e: MarkerEvent<mapboxgl.Marker, MouseEvent>, pinDoc: Doc) => {
        this._featuresFromGeocodeResults = [];
        this.deselectPinOrRoute(); // TODO: check this method
        this._selectedPinOrRoute = pinDoc;
        // this.bingSearchBarContents = pinDoc.map;

        // Doc.setDocFilter(this.Document, 'latitude', this.selectedPin.latitude, 'match');
        // Doc.setDocFilter(this.Document, 'longitude', this.selectedPin.longitude, 'match');
        Doc.setDocFilter(this.Document, LinkedTo, `mapPin=${Field.toScriptString(this._selectedPinOrRoute)}`, 'check');

        this.recolorPin(this._selectedPinOrRoute, 'green'); // TODO: check this method

        MapAnchorMenu.Instance.Delete = this.deleteSelectedPinOrRoute;
        MapAnchorMenu.Instance.Center = this.centerOnSelectedPin;
        MapAnchorMenu.Instance.OnClick = this.createNoteAnnotation;
        MapAnchorMenu.Instance.StartDrag = this.startAnchorDrag;

        MapAnchorMenu.Instance.Reset();

        // pass in the pinDoc
        MapAnchorMenu.Instance.setPinDoc(pinDoc);
        MapAnchorMenu.Instance.setAllMapboxPins(this.allAnnotations.filter(anno => !anno.layout_unrendered));

        MapAnchorMenu.Instance.DisplayRoute = this.displayRoute;
        MapAnchorMenu.Instance.AddNewRouteToMap = this.createMapRoute;
        MapAnchorMenu.Instance.CreatePin = this.addMarkerForFeature;

        MapAnchorMenu.Instance.setMenuType('standard');

        // MapAnchorMenu.Instance.jumpTo(NumCast(pinDoc.longitude), NumCast(pinDoc.latitude)-3, true);

        MapAnchorMenu.Instance.jumpTo(e.originalEvent.clientX, e.originalEvent.clientY, true);

        document.addEventListener('pointerdown', this.tryHideMapAnchorMenu, true);

        // this._mapRef.current.flyTo({
        //     center: [NumCast(pinDoc.longitude), NumCast(pinDoc.latitude)-3]
        // })
    };

    @action
    displayRoute = (routeInfoMap: Record<TransportationType, any> | undefined, type: TransportationType) => {
        if (routeInfoMap) {
            const newTempRouteSource: FeatureCollection = {
                type: 'FeatureCollection',
                features: [
                    {
                        type: 'Feature',
                        properties: {},
                        geometry: {
                            type: 'LineString',
                            coordinates: routeInfoMap[type].coordinates,
                        },
                    },
                ],
            };
            // TODO: Create pin for destination
            // TODO: Fly to point where full route will be shown
            this._temporaryRouteSource = newTempRouteSource;
        }
    };

    @action
    setAnimationPhase = (newValue: number) => {
        this._animationPhase = newValue;
    };

    @action
    setFrameId = (frameId: number) => {
        this._frameId = frameId;
    };

    @action
    setAnimationUtility = (util: AnimationUtility) => {
        this._animationUtility = util;
    };

    @action
    openAnimationPanel = (routeDoc: Doc | undefined) => {
        if (routeDoc) {
            MapAnchorMenu.Instance.fadeOut(true);
            document.removeEventListener('pointerdown', this.tryHideMapAnchorMenu, true);
            this._featuresFromGeocodeResults = [];
            this._routeToAnimate = routeDoc;
        }
    };

    @computed get mapboxMapViewState(): ViewState {
        return {
            zoom: NumCast(this.dataDoc.map_zoom, 8),
            longitude: NumCast(this.dataDoc.longitude, -71.4128),
            latitude: NumCast(this.dataDoc.latitude, 41.824),
            pitch: NumCast(this.dataDoc.map_pitch),
            bearing: NumCast(this.dataDoc.map_bearing),
            padding: {
                top: 0,
                bottom: 0,
                left: 0,
                right: 0,
            },
        };
    }

    @computed
    get preAnimationViewState() {
        if (!this._isAnimating) {
            return this.mapboxMapViewState;
        }
        return undefined;
    }

    @action
    setAnimationLineColor = (color: ColorResult) => {
        this._animationLineColor = color.hex;
    };

    @action
    updateAnimationSpeed = () => {
        let newAnimationSpeed: AnimationSpeed;
        switch (this._animationSpeed) {
            case AnimationSpeed.SLOW:
                newAnimationSpeed = AnimationSpeed.MEDIUM;
                break;
            case AnimationSpeed.MEDIUM:
                newAnimationSpeed = AnimationSpeed.FAST;
                break;
            case AnimationSpeed.FAST:
                newAnimationSpeed = AnimationSpeed.SLOW;
                break;
            default:
                newAnimationSpeed = AnimationSpeed.MEDIUM;
                break;
        }
        this._animationSpeed = newAnimationSpeed;
        if (this._animationUtility) {
            this._animationUtility.updateAnimationSpeed(newAnimationSpeed);
        }
    };
    @computed get animationSpeedTooltipText(): string {
        switch (this._animationSpeed) {
            case AnimationSpeed.SLOW:   return '1x speed';
            case AnimationSpeed.MEDIUM: return '2x speed';
            case AnimationSpeed.FAST:   return '3x speed';
            default:                    return '2x speed';
        } // prettier-ignore
    }
    @computed get animationSpeedIcon(): JSX.Element {
        switch (this._animationSpeed) {
            case AnimationSpeed.SLOW:   return slowSpeedIcon;
            case AnimationSpeed.MEDIUM: return mediumSpeedIcon;
            case AnimationSpeed.FAST:   return fastSpeedIcon;
            default:                    return mediumSpeedIcon;
        } // prettier-ignore
    }

    @action
    toggleIsStreetViewAnimation = () => {
        const newVal = !this._isStreetViewAnimation;
        this._isStreetViewAnimation = newVal;
        this._animationUtility?.updateIsStreetViewAnimation(newVal);
    };

    getFeatureFromRouteDoc = (routeDoc: Doc): Feature<Geometry, GeoJsonProperties> => {
        const geometry: LineString = {
            type: 'LineString',
            coordinates: JSON.parse(StrCast(routeDoc.routeCoordinates)),
        };
        return {
            type: 'Feature',
            properties: {
                routeTitle: routeDoc.title,
            },
            geometry: geometry,
        };
    };

    @action
    playAnimation = (status: AnimationStatus) => {
        if (!this._mapRef.current || !this._routeToAnimate) {
            return;
        }

        this._animationPhase = status === AnimationStatus.RESUME ? this._animationPhase : 0;
        this._frameId = AnimationStatus.RESUME ? this._frameId : null;
        this._finishedFlyTo = AnimationStatus.RESUME ? this._finishedFlyTo : false;

        const path = turf.lineString(this.selectedRouteCoordinates);

        this._settingsOpen = false;
        this.path = path;
        this._isAnimating = true;

        runInAction(
            () =>
                // eslint-disable-next-line no-async-promise-executor
                new Promise<void>(async resolve => {
                    const targetLngLat = {
                        lng: this.selectedRouteCoordinates[0][0],
                        lat: this.selectedRouteCoordinates[0][1],
                    };

                    const animationUtil = new AnimationUtility(targetLngLat, this.selectedRouteCoordinates, this._isStreetViewAnimation, this._animationSpeed, this._showTerrain, this._mapRef.current);
                    runInAction(() => this.setAnimationUtility(animationUtil));

                    const updateFrameId = (newFrameId: number) => this.setFrameId(newFrameId);
                    const updateAnimationPhase = (newAnimationPhase: number) => this.setAnimationPhase(newAnimationPhase);

                    if (status !== AnimationStatus.RESUME) {
                        const result = await animationUtil.flyInAndRotate({
                            map: this._mapRef.current!,
                            // targetLngLat,
                            // duration 3000
                            // startAltitude: 3000000,
                            // endAltitude: this.isStreetViewAnimation ? 80 : 12000,
                            // startBearing: 0,
                            // endBearing: -20,
                            // startPitch: 40,
                            // endPitch: this.isStreetViewAnimation ? 80 : 50,
                            updateFrameId,
                        });

                        console.log('Bearing: ', result.bearing);
                        console.log('Altitude: ', result.altitude);
                    }

                    runInAction(() => {
                        this._finishedFlyTo = true;
                    });

                    // follow the path while slowly rotating the camera, passing in the camera bearing and altitude from the previous animation
                    await animationUtil.animatePath({
                        map: this._mapRef.current!,
                        // path: this.path,
                        // startBearing: -20,
                        // startAltitude: this.isStreetViewAnimation ? 80 : 12000,
                        // pitch: this.isStreetViewAnimation ? 80: 50,
                        currentAnimationPhase: this._animationPhase,
                        updateAnimationPhase,
                        updateFrameId,
                    });

                    // get the bounds of the linestring, use fitBounds() to animate to a final view
                    const bbox3d = turf.bbox(this.path);

                    const bbox2d: LngLatBoundsLike = [bbox3d[0], bbox3d[1], bbox3d[2], bbox3d[3]];

                    this._mapRef.current!.fitBounds(bbox2d, {
                        duration: 3000,
                        pitch: 30,
                        bearing: 0,
                        padding: 120,
                    });

                    setTimeout(() => {
                        this._isStreetViewAnimation = false;
                        resolve();
                    }, 10000);
                })
        );
    };

    @action
    pauseAnimation = () => {
        if (this._frameId && this._animationPhase > 0) {
            window.cancelAnimationFrame(this._frameId);
            this._frameId = null;
            this._isAnimating = false;
        }
    };

    @action
    stopAnimation = (close: boolean) => {
        if (this._frameId) {
            window.cancelAnimationFrame(this._frameId);
        }
        this._animationPhase = 0;
        this._frameId = null;
        this._finishedFlyTo = false;
        this._isAnimating = false;
        if (close) {
            this._animationSpeed = AnimationSpeed.MEDIUM;
            this._isStreetViewAnimation = false;
            this._routeToAnimate = undefined;
            this._animationUtility = null;
        }
    };

    getRouteAnimationOptions = (): JSX.Element => (
        <>
            <IconButton
                tooltip={this._isAnimating && this._finishedFlyTo ? 'Pause Animation' : 'Play Animation'}
                onPointerDown={() => {
                    if (this._isAnimating && this._finishedFlyTo) {
                        this.pauseAnimation();
                    } else if (this._animationPhase > 0) {
                        this.playAnimation(AnimationStatus.RESUME); // Resume from the current phase
                    } else {
                        this.playAnimation(AnimationStatus.START); // Play from the beginning
                    }
                }}
                icon={this._isAnimating && this._finishedFlyTo ? <FontAwesomeIcon icon={faPause as IconLookup} /> : <FontAwesomeIcon icon={faPlay as IconLookup} />}
                color="black"
                size={Size.MEDIUM}
            />
            {this._isAnimating && this._finishedFlyTo && (
                <IconButton
                    tooltip="Restart animation"
                    onPointerDown={() => {
                        this.stopAnimation(false);
                        this.playAnimation(AnimationStatus.START);
                    }}
                    icon={<FontAwesomeIcon icon={faRotate as IconLookup} />}
                    color="black"
                    size={Size.MEDIUM}
                />
            )}
            <IconButton style={{ marginRight: '10px' }} tooltip="Stop and close animation" onPointerDown={() => this.stopAnimation(true)} icon={<FontAwesomeIcon icon={faCircleXmark as IconLookup} />} color="black" size={Size.MEDIUM} />
            <div className="animation-suboptions">
                <div>|</div>
                <FormControlLabel className="first-person-label" label="1st person animation:" labelPlacement="start" control={<Checkbox color="success" checked={this._isStreetViewAnimation} onChange={this.toggleIsStreetViewAnimation} />} />
                <div id="divider">|</div>
                <IconButton tooltip={this.animationSpeedTooltipText} onPointerDown={this.updateAnimationSpeed} icon={this.animationSpeedIcon} size={Size.MEDIUM} />
                <div id="divider">|</div>
                <div style={{ display: 'flex', alignItems: 'center' }}>
                    <div>Select Line Color: </div>
                    <CirclePicker circleSize={12} circleSpacing={5} width="100%" colors={['#ffff00', '#03a9f4', '#ff0000', '#ff5722', '#000000', '#673ab7']} onChange={(color: any) => this.setAnimationLineColor(color)} />
                </div>
            </div>
        </>
    );

    @action
    hideRoute = () => {
        this._temporaryRouteSource = {
            type: 'FeatureCollection',
            features: [],
        };
    };

    @action
    toggleSettings = () => {
        if (!this._isAnimating && this._animationPhase === 0) {
            this._featuresFromGeocodeResults = [];
            this._settingsOpen = !this._settingsOpen;
        }
    };

    @action
    changeMapStyle = (e: React.ChangeEvent<HTMLSelectElement>) => {
        this.dataDoc.map_style = e.target.value;
        // this.mapStyle = `mapbox://styles/mapbox/${e.target.value}`
    };

    @action
    onBearingChange = (e: React.ChangeEvent<HTMLInputElement>) => {
        const bearing = parseInt(e.target.value);
        if (!isNaN(bearing) && this._mapRef.current) {
            console.log('bearing change');
            const fixedBearing = Math.max(0, Math.min(360, bearing));
            this._mapRef.current.setBearing(fixedBearing);
            this.dataDoc.map_bearing = fixedBearing;
        }
    };

    @action
    onPitchChange = (e: React.ChangeEvent<HTMLInputElement>) => {
        const pitch = parseInt(e.target.value);
        if (!isNaN(pitch) && this._mapRef.current) {
            console.log('pitch change');
            const fixedPitch = Math.max(0, Math.min(85, pitch));
            this._mapRef.current.setPitch(fixedPitch);
            this.dataDoc.map_pitch = fixedPitch;
        }
    };

    @action
    onZoomChange = (e: React.ChangeEvent<HTMLInputElement>) => {
        const zoom = parseInt(e.target.value);
        if (!isNaN(zoom) && this._mapRef.current) {
            const fixedZoom = Math.max(0, Math.min(16, zoom));
            this._mapRef.current.setZoom(fixedZoom);
            this.dataDoc.map_zoom = fixedZoom;
        }
    };

    @action
    onStepZoomChange = (increment: boolean) => {
        if (this._mapRef.current) {
            const newZoom = increment //
                ? Math.min(16, this.mapboxMapViewState.zoom + 1)
                : Math.max(0, this.mapboxMapViewState.zoom - 1);

            this._mapRef.current.setZoom(newZoom);
            this.dataDoc.map_zoom = newZoom;
        }
    };

    @action
    onMapZoom = (e: ViewStateChangeEvent) => {
        this.dataDoc.map_zoom = e.viewState.zoom;
    };

    @action
    onMapMove = (e: ViewStateChangeEvent) => {
        this.dataDoc.longitude = e.viewState.longitude;
        this.dataDoc.latitude = e.viewState.latitude;
    };

    @action
    toggleShowTerrain = () => {
        this._showTerrain = !this._showTerrain;
    };

    getMarkerIcon = (pinDoc: Doc): JSX.Element | null => {
        const markerType = StrCast(pinDoc.markerType);
        const markerColor = StrCast(pinDoc.markerColor);

        return MarkerIcons.getFontAwesomeIcon(markerType, '2x', markerColor) ?? null;
    };

    _textRef = React.createRef<any>();
    render() {
        const scale = this._props.NativeDimScaling?.() || 1;
        const parscale = scale === 1 ? 1 : (this.ScreenToLocalBoxXf().Scale ?? 1);

        return (
            <div className="mapBox" ref={this._ref}>
                <div
                    className="mapBox-wrapper"
                    onWheel={e => e.stopPropagation()}
                    onPointerDown={e => e.button === 0 && !e.ctrlKey && e.stopPropagation()}
                    style={{ transformOrigin: 'top left', transform: `scale(${scale})`, width: `calc(100% - ${this.sidebarWidthPercent})`, pointerEvents: this.pointerEvents() }}>
                    {!this._routeToAnimate && (
                        <div className="mapBox-searchbar" style={{ width: `${100 / scale}%`, zIndex: 1, position: 'relative', background: 'lightGray' }}>
                            <TextField ref={this._textRef} fullWidth placeholder="Enter a location" onKeyDown={this.searchbarKeyDown} onChange={(e: any) => this.handleSearchChange(e.target.value)} />
                            <IconButton icon={<FontAwesomeIcon icon={faGear as IconLookup} size="1x" />} type={Type.TERT} onClick={() => this.toggleSettings()} />
                            <div style={{ opacity: 0 }}>
                                <IconButton icon={<FontAwesomeIcon icon={faGear as IconLookup} size="1x" />} type={Type.TERT} onClick={() => this.toggleSettings()} />
                            </div>
                        </div>
                    )}
                    {this._settingsOpen && !this._routeToAnimate && (
                        <div className="mapbox-settings-panel" style={{ right: `${0 + this.sidebarWidth()}px` }}>
                            <div className="mapbox-style-select">
                                <div>Map Style:</div>
                                <div>
                                    <select onChange={this.changeMapStyle} value={StrCast(this.dataDoc.map_style)}>
                                        <option value="mapbox://styles/mapbox/standard">Standard</option>
                                        <option value="mapbox://styles/mapbox/streets-v11">Streets</option>
                                        <option value="mapbox://styles/mapbox/outdoors-v12">Outdoors</option>
                                        <option value="mapbox://styles/mapbox/light-v11">Light</option>
                                        <option value="mapbox://styles/mapbox/dark-v11">Dark</option>
                                        <option value="mapbox://styles/mapbox/satellite-v9">Satellite</option>
                                        <option value="mapbox://styles/mapbox/satellite-streets-v12">Satellite Streets</option>
                                        <option value="mapbox://styles/mapbox/navigation-day-v1">Navigation Day</option>
                                        <option value="mapbox://styles/mapbox/navigation-night-v1">Navigation Night</option>
                                    </select>
                                </div>
                            </div>
                            <div className="mapbox-bearing-selection">
                                <div>Bearing: </div>
                                <input value={NumCast(this.mapboxMapViewState.bearing).toFixed(0)} type="number" onChange={this.onBearingChange} />
                            </div>
                            <div className="mapbox-pitch-selection">
                                <div>Pitch: </div>
                                <input value={NumCast(this.mapboxMapViewState.pitch).toFixed(0)} type="number" onChange={this.onPitchChange} />
                            </div>
                            <div className="mapbox-pitch-selection">
                                <div>Zoom: </div>
                                <input value={NumCast(this.mapboxMapViewState.zoom).toFixed(0)} type="number" onChange={this.onZoomChange} />
                            </div>
                            <div className="mapbox-terrain-selection">
                                <div>Show terrain: </div>
                                <input type="checkbox" checked={this._showTerrain} onChange={this.toggleShowTerrain} />
                            </div>
                        </div>
                    )}
                    {this._routeToAnimate && (
                        <div className="animation-panel" style={{ width: this.sidebarWidth() === 0 ? '100%' : `calc(100% - ${this.sidebarWidth()}px)` }}>
                            <div id="route-to-animate-title">{StrCast(this._routeToAnimate.title)}</div>
                            <div className="route-animation-options">{this.getRouteAnimationOptions()}</div>
                        </div>
                    )}
                    {this._featuresFromGeocodeResults.length > 0 && (
                        <div className="mapbox-geocoding-search-results">
                            <h4>Choose a location for your pin: </h4>
                            {this._featuresFromGeocodeResults
                                .filter(feature => feature.place_name)
                                .map((feature, idx) => (
                                    <div
                                        // eslint-disable-next-line react/no-array-index-key
                                        key={idx}
                                        className="search-result-container"
                                        onClick={() => {
                                            this.handleSearchChange('');
                                            this.addMarkerForFeature(feature);
                                        }}>
                                        <div className="search-result-place-name">{feature.place_name}</div>
                                    </div>
                                ))}
                        </div>
                    )}
                    <MapProvider>
                        <MapboxMap
                            ref={this._mapRef}
                            mapboxAccessToken={MAPBOX_ACCESS_TOKEN}
                            viewState={this._isAnimating || this._routeToAnimate ? undefined : { ...this.mapboxMapViewState, width: NumCast(this.layoutDoc._width), height: NumCast(this.layoutDoc._height) }}
                            mapStyle={this.dataDoc.map_style ? StrCast(this.dataDoc.map_style) : 'mapbox://styles/mapbox/streets-v11'}
                            style={{
                                position: 'absolute',
                                top: 0,
                                left: 0,
                                zIndex: '0',
                                width: NumCast(this.layoutDoc._width) * parscale,
                                height: NumCast(this.layoutDoc._height) * parscale,
                            }}
                            initialViewState={this._isAnimating ? undefined : this.mapboxMapViewState}
                            onZoom={this.onMapZoom}
                            onMove={this.onMapMove}
                            onClick={this.handleMapClick}
                            onDblClick={this.handleMapDblClick}
                            terrain={this._showTerrain ? { source: 'mapbox-dem', exaggeration: 2.0 } : undefined}>
                            <Source id="mapbox-dem" type="raster-dem" url="mapbox://mapbox.mapbox-terrain-dem-v1" tileSize={512} maxzoom={14} />
                            <Source id="temporary-route" type="geojson" data={this._temporaryRouteSource} />
                            <Source id="map-routes" type="geojson" data={this.allRoutesGeoJson} />
                            <Layer id="temporary-route-layer" type="line" source="temporary-route" layout={{ 'line-join': 'round', 'line-cap': 'round' }} paint={{ 'line-color': '#36454F', 'line-width': 4, 'line-dasharray': [1, 1] }} />
                            {!this._isAnimating && this._animationPhase === 0 && (
                                <Layer id="map-routes-layer" type="line" source="map-routes" layout={{ 'line-join': 'round', 'line-cap': 'round' }} paint={{ 'line-color': '#FF0000', 'line-width': 4 }} />
                            )}
                            {this._routeToAnimate && (this._isAnimating || this._animationPhase > 0) && (
                                <>
                                    {!this._isStreetViewAnimation && (
                                        <>
                                            <Source id="animated-route" type="geojson" data={this.updatedRouteCoordinates} />
                                            <Layer
                                                id="dynamic-animation-line"
                                                type="line"
                                                source="animated-route"
                                                paint={{
                                                    'line-color': this._animationLineColor,
                                                    'line-width': 5,
                                                }}
                                            />
                                        </>
                                    )}
                                    <Source id="start-pin-base" type="geojson" data={AnimationUtility.createGeoJSONCircle(this.selectedRouteCoordinates[0], 0.04)} />
                                    <Source id="start-pin-top" type="geojson" data={AnimationUtility.createGeoJSONCircle(this.selectedRouteCoordinates[0], 0.25)} />
                                    <Source id="end-pin-base" type="geojson" data={AnimationUtility.createGeoJSONCircle(this.selectedRouteCoordinates.slice(-1)[0], 0.04)} />
                                    <Source id="end-pin-top" type="geojson" data={AnimationUtility.createGeoJSONCircle(this.selectedRouteCoordinates.slice(-1)[0], 0.25)} />
                                    <Layer
                                        id="start-fill-pin-base"
                                        type="fill-extrusion"
                                        source="start-pin-base"
                                        paint={{
                                            'fill-extrusion-color': '#0bfc03',
                                            'fill-extrusion-height': 1000,
                                        }}
                                    />
                                    <Layer
                                        id="start-fill-pin-top"
                                        type="fill-extrusion"
                                        source="start-pin-top"
                                        paint={{
                                            'fill-extrusion-color': '#0bfc03',
                                            'fill-extrusion-base': 1000,
                                            'fill-extrusion-height': 1200,
                                        }}
                                    />
                                    <Layer
                                        id="end-fill-pin-base"
                                        type="fill-extrusion"
                                        source="end-pin-base"
                                        paint={{
                                            'fill-extrusion-color': '#eb1c1c',
                                            'fill-extrusion-height': 1000,
                                        }}
                                    />
                                    <Layer
                                        id="end-fill-pin-top"
                                        type="fill-extrusion"
                                        source="end-pin-top"
                                        paint={{
                                            'fill-extrusion-color': '#eb1c1c',
                                            'fill-extrusion-base': 1000,
                                            'fill-extrusion-height': 1200,
                                        }}
                                    />
                                </>
                            )}

                            {!this._isAnimating &&
                                this._animationPhase === 0 &&
                                this.allPushpins // .filter(anno => !anno.layout_unrendered)
                                    .map((pushpin, idx) => (
                                        // eslint-disable-next-line react/no-array-index-key
                                        <Marker key={idx} longitude={NumCast(pushpin.longitude)} latitude={NumCast(pushpin.latitude)} anchor="bottom" onClick={(e: MarkerEvent<mapboxgl.Marker, MouseEvent>) => this.handleMarkerClick(e, pushpin)}>
                                            {this.getMarkerIcon(pushpin)}
                                        </Marker>
                                    ))}

                            {/* {this.mapMarkers.length > 0 && this.mapMarkers.map((marker, idx) => (
                                <Marker key={idx} longitude={marker.longitude} latitude={marker.latitude}/>
                            ))} */}
                        </MapboxMap>
                    </MapProvider>
                </div>
                <div className="mapBox-sidebar" style={{ width: `${this.sidebarWidthPercent}`, backgroundColor: `${this.sidebarColor}` }}>
                    <SidebarAnnos
                        ref={this._sidebarRef}
                        // eslint-disable-next-line react/jsx-props-no-spreading
                        {...this._props}
                        fieldKey={this.fieldKey}
                        Document={this.Document}
                        layoutDoc={this.layoutDoc}
                        dataDoc={this.dataDoc}
                        usePanelWidth
                        showSidebar={this.SidebarShown}
                        nativeWidth={NumCast(this.layoutDoc._nativeWidth)}
                        whenChildContentsActiveChanged={this.whenChildContentsActiveChanged}
                        PanelWidth={this.sidebarWidth}
                        sidebarAddDocument={this.sidebarAddDocument}
                        moveDocument={this.moveDocument}
                        removeDocument={this.sidebarRemoveDocument}
                    />
                </div>
                {this.sidebarHandle}
            </div>
        );
    }
}

Docs.Prototypes.TemplateMap.set(DocumentType.MAP, {
    layout: { view: MapBox, dataField: 'data' },
    options: { acl: '', map: '', _height: 600, _width: 800, _layout_reflowHorizontal: true, _layout_reflowVertical: true, _layout_nativeDimEditable: true, systemIcon: 'BsFillPinMapFill' },
});
