import {
    Camera,
    CameraRef,
    MapView,
    MapViewRef,
    RegionPayload,
    UserLocation,
    UserLocationRef
} from "@maplibre/maplibre-react-native";
import { useQuery } from "@tanstack/react-query";
import { Text, StyleSheet, Dimensions } from "react-native";
import { useEffect } from "react";
import { PersistQueryClientProvider } from '@tanstack/react-query-persist-client'
import {viewport} from '@placemarkio/geo-viewport';
import type { Feature } from "geojson";
import type { Location } from "@maplibre/maplibre-react-native";

import type { MapParameters, MapViewProps } from "./types";
import { queryClient, persister } from "./api";
import { getSnippet, mapKeys } from "./api/livemap-service";
import { applyFiltersToLayer, getStyle } from "./api/style-service";
import { styleKeys } from "./api/style-service";

import { deltaToBounds } from "./utils/map";
import { useCallbackRef } from "./hooks/useCallbackRef";

import POIManager from "./core/poi-manager";
import OfflineManager from "./core/offline-manager";

import { DEFAULT_TILES_STYLE } from "./core/constants";

import { useSettingsStore } from "./storages/settings";
import { usePOIsStore } from "./storages/pois";

import Polylines from "./components/polylines";
import OpenedPOIs from './components/opened-pois';

const screenDimensions = Dimensions.get('screen');

const WemapMapView = ({ emmid, ...props }: MapViewProps) => {
    const { isPending, error, data: mapParameter } = useQuery<MapParameters>({
        queryKey: mapKeys.snippet(emmid),
        queryFn: () => getSnippet(emmid)
    });

    const {data: style} = useQuery({
        queryKey: styleKeys.style(mapParameter ? mapParameter.snippet.tilesstyle || DEFAULT_TILES_STYLE : ''),
        queryFn: () => {
            if (!mapParameter) {
                return null;
            }

            return getStyle(mapParameter?.snippet.tilesstyle || DEFAULT_TILES_STYLE);
        },
    });

    const openedPOIs = usePOIsStore((state) => state.openedPOIs);
    const openPOI = usePOIsStore((state) => state.openPOI);
    const closeAll = usePOIsStore((state) => state.closeAll);

    const bounds = deltaToBounds(mapParameter);

    const [mapViewRef, mapViewCallback] = useCallbackRef<MapViewRef>();
    const [cameraRef, cameraCallback] = useCallbackRef<CameraRef>();
    const [userLocationRef, userLocationCallback] = useCallbackRef<UserLocationRef>();

    const predictedCenterAndZoom = viewport(bounds.toArray(), [screenDimensions.width, screenDimensions.height], {
        minzoom: mapParameter?.snippet.minzoom,
        maxzoom: mapParameter?.snippet.maxzoom,
        allowFloat: true
    });

    useEffect(() => {
        if (mapViewRef && cameraRef && userLocationRef) {
            props.onRefsReady?.({
                mapView: mapViewRef,
                camera: cameraRef,
                userLocation: userLocationRef
            });
        }
    }, [mapViewRef, cameraRef, userLocationRef]);

    useEffect(() => {
        if (mapParameter) {
            OfflineManager.createPack(mapParameter);
        }
    }, [mapParameter]);


    const onRegionDidChange = (region: Feature<GeoJSON.Point, RegionPayload>) => {
        props.onRegionDidChange?.(region);

        const pois = POIManager.getByBounds(region.properties.visibleBounds);
        props.onPOIsUpdate?.(pois);
    };

    const onPress = async (feature: Feature) => {

        props.onPress?.(feature);
        if (openedPOIs.length > 0) {
            closeAll();
        }

        if (mapViewRef && feature.properties) {
            const featuresCollection = await mapViewRef.queryRenderedFeaturesAtPoint([
                feature.properties.screenPointX,
                feature.properties.screenPointY,
            ], undefined, []);

            const pinpointFeature = featuresCollection.features.find((feature) => feature.properties?.pinpoint);
            if (pinpointFeature) {
                const poi = POIManager.getByID(Number(pinpointFeature.properties?.pinpoint));
                if (poi) {
                    openPOI(poi);
                    setTimeout(() => {
                        cameraRef?.moveTo([poi.longitude, poi.latitude], 500);
                    }, 100)
                    props.onPOIOpen?.(poi);
                }
            }
        }
    }

    const onUserLocationUpdate = (location: Location) => {
        props.onUserLocationUpdate?.(location);
    }

    if (isPending || !style) return null;
    if (error && !mapParameter) {
        console.error(error);
        return <Text>An error occurred</Text>;
    }
    if (!mapParameter) return null;

    return (
        <MapView {...props}
            style={[styles.container, props.style]}
            ref={mapViewCallback}
            onPress={onPress}
            regionDidChangeDebounceTime={100}
            onRegionDidChange={onRegionDidChange}
            mapStyle={{
                ...style,
                layers: applyFiltersToLayer(style.layers, {
                    hiddenPOIs: openedPOIs.map((poi) => poi.id)
                })
            }}>
            <UserLocation
                onUpdate={onUserLocationUpdate}
                ref={userLocationCallback}
                renderMode="normal"
                androidRenderMode="normal" />
            <Camera
                ref={cameraCallback}
                defaultSettings={{
                    centerCoordinate: [mapParameter.longitude, mapParameter.latitude],
                    zoomLevel: predictedCenterAndZoom.zoom,
                    pitch: 0,
                    heading: 0
                }}
                minZoomLevel={mapParameter.snippet.minzoom}
                maxZoomLevel={mapParameter.snippet.maxzoom} />
            <Polylines />
            <OpenedPOIs />
            {props.children}
        </MapView>
    );
};

const styles = StyleSheet.create({
    container: {
        flex: 1,
    }
});

const Root = (props: MapViewProps) => {
    const setEmmid = useSettingsStore((state) => state.setEmmid);
    useEffect(() => {
        setEmmid(props.emmid);
        POIManager.fetchAll();
    }, [props.emmid]);

    return (
        <PersistQueryClientProvider
            client={queryClient}
            persistOptions={{ persister }}
            onSuccess={() => {
                // resume mutations after initial restore from localStorage was successful
                queryClient.resumePausedMutations().then(() => {
                    queryClient.invalidateQueries()
                })
            }}>
            <WemapMapView {...props} />
        </PersistQueryClientProvider>
    );
};

export default Root;
