import ThemeConfig from "../ThemeConfig/ThemeConfig"
import { WithChangesState } from "./WithChangesState"
import FavouritesFeatureSource from "../../Logic/FeatureSource/Sources/FavouritesFeatureSource"
import Constants from "../Constants"
import { FeatureSource } from "../../Logic/FeatureSource/FeatureSource"
import StaticFeatureSource from "../../Logic/FeatureSource/Sources/StaticFeatureSource"
import { Feature } from "geojson"
import { BBox } from "../../Logic/BBox"
import ShowDataLayer from "../../UI/Map/ShowDataLayer"
import MetaTagging from "../../Logic/MetaTagging"
import FilteredLayer from "../FilteredLayer"
import LayerConfig from "../ThemeConfig/LayerConfig"
import { LayerConfigJson } from "../ThemeConfig/Json/LayerConfigJson"
import last_click_layerconfig from "../../../public/assets/generated/layers/last_click.json"
import { GeoOperations } from "../../Logic/GeoOperations"
import summaryLayer from "../../../public/assets/generated/layers/summary.json"
import { Store, UIEventSource } from "../../Logic/UIEventSource"
import NearbyFeatureSource from "../../Logic/FeatureSource/Sources/NearbyFeatureSource"
import {
    SummaryTileSource,
    SummaryTileSourceRewriter,
} from "../../Logic/FeatureSource/TiledFeatureSource/SummaryTileSource"
import { ShowDataLayerOptions } from "../../UI/Map/ShowDataLayerOptions"

export class WithSpecialLayers extends WithChangesState {
    readonly favourites: FavouritesFeatureSource
    /**
     * When hovering (in the popup) an image, the location of the image will be revealed on the main map.
     * This store contains those images that should be shown, probably only the currently hovered image
     */
    readonly geocodedImages: UIEventSource<Feature[]> = new UIEventSource<Feature[]>([])
    /**
     * Contains a few (<10) >features that are near the center of the map.
     */
    readonly closestFeatures: NearbyFeatureSource

    readonly featureSummary: SummaryTileSourceRewriter
    /**
     * When using arrow keys to move, the accessibility mode is activated, which has a small rectangle set.
     * This is the 'viewport' which 'closestFeatures' uses to filter wilt
     */
    readonly visualFeedbackViewportBounds: UIEventSource<BBox> = new UIEventSource<BBox>(undefined)

    constructor(theme: ThemeConfig, mvtAvailableLayers: Store<Set<string>>) {
        super(theme, mvtAvailableLayers)

        this.favourites = new FavouritesFeatureSource(this)

        this.closestFeatures = new NearbyFeatureSource(
            this.mapProperties.location,
            this.perLayerFiltered,
            {
                currentZoom: this.mapProperties.zoom,
                layerState: this.layerState,
                bounds: this.visualFeedbackViewportBounds.map(
                    (bounds) => bounds ?? this.mapProperties.bounds?.data,
                    [this.mapProperties.bounds]
                ),
            }
        )
        this.closestFeatures.registerSource(this.favourites, "favourite")

        this.featureSummary = this.setupSummaryLayer()
        this.initActorsSpecialLayers()
        this.drawSelectedElement()
        this.drawSpecialLayers()
        try {
            this.drawLastClick()
        } catch (e) {
            console.error("FATAL: invalid last click layer", e)
            throw e
        }
        // Note: the lock-range is handled by UserMapFeatureSwitchState
        {
            // Activate metatagging for the 'current_view' layer
            const currentViewLayer = this.layerState.filteredLayers.get("current_view")?.layerDef
            if (currentViewLayer?.tagRenderings?.length > 0) {
                const params = MetaTagging.createExtraFuncParams(this)
                this.currentView.features.addCallbackAndRunD((features) => {
                    MetaTagging.addMetatags(
                        features,
                        params,
                        currentViewLayer,
                        this.theme,
                        this.osmObjectDownloader,
                        this.featureProperties
                    )
                })
            }
        }
    }

    private setupSummaryLayer(): SummaryTileSourceRewriter | undefined {
        /**
         * MaxZoom for the summary layer
         */
        const normalLayers = this.theme.layers.filter((l) => l.isNormal())

        const maxzoom = Math.min(...normalLayers.map((l) => l.minzoom))

        const layers = this.theme.layers.filter(
            (l) => !Constants.isPriviliged(l) && l.source.geojsonSource === undefined && l.doCount
        )
        if (!Constants.SummaryServer || layers.length === 0) {
            return undefined
        }
        const summaryTileSource = new SummaryTileSource(
            Constants.SummaryServer,
            layers.map((l) => l.id),
            this.mapProperties.zoom.map((z) => Math.max(Math.floor(z), 0)),
            this.mapProperties,
            {
                isActive: this.mapProperties.zoom.map((z) => z < maxzoom),
            }
        )

        const source = new SummaryTileSourceRewriter(
            summaryTileSource,
            this.layerState.filteredLayers
        )

        new ShowDataLayer(this.map, {
            features: source,
            layer: new LayerConfig(<LayerConfigJson>(<unknown>summaryLayer), "summaryLayer"),
            // doShowLayer: this.mapProperties.zoom.map((z) => z < maxzoom),
            selectedElement: this.selectedElement,
        })
        return source
    }

    protected registerSpecialLayer(flayer: FilteredLayer, source: FeatureSource) {
        if (!source?.features) {
            return
        }

        this.featureProperties.trackFeatureSource(source)
        const options: ShowDataLayerOptions & { layer: LayerConfig } = {
            features: source,
            doShowLayer: flayer.isDisplayed,
            layer: flayer.layerDef,
            metaTags: this.userRelatedState.preferencesAsTags,
            selectedElement: this.selectedElement,
        }
        new ShowDataLayer(this.map, options)
    }

    private drawLastClick() {
        const source = this.lastClickObject
        const lastClickLayerConfig = new LayerConfig(
            <LayerConfigJson>(<unknown>last_click_layerconfig),
            "last_click"
        )
        const lastClickFiltered =
            lastClickLayerConfig.isShown === undefined
                ? source
                : source.features.mapD((fs) =>
                      fs.filter((f) => {
                          const matches = lastClickLayerConfig.isShown.matchesProperties(
                              f.properties
                          )
                          console.debug("LastClick ", f, "matches", matches)
                          return matches
                      })
                  )
        // show last click = new point/note marker
        const features = new StaticFeatureSource(lastClickFiltered)
        this.featureProperties.trackFeatureSource(features)
        new ShowDataLayer(this.map, {
            features,
            layer: lastClickLayerConfig,
            onClick: (feature) => {
                if (this.mapProperties.zoom.data >= Constants.minZoomLevelToAddNewPoint) {
                    this.selectedElement.setData(feature)
                    return
                }
                this.map.data.flyTo({
                    zoom: Constants.minZoomLevelToAddNewPoint,
                    center: GeoOperations.centerpointCoordinates(feature),
                })
            },
        })
    }

    private drawSelectedElement() {
        const src = new StaticFeatureSource(
            this.selectedElement.map((f) => (f === undefined ? [] : [f]))
        )
        ShowDataLayer.showMultipleLayers(this.map, src, this.theme.layers, {
            prefix: "selected_element_",
        })
    }

    private drawSpecialLayers() {
        type AddedByDefaultTypes = (typeof Constants.added_by_default)[number]
        type LayersToAdd =
            | "current_view"
            | Exclude<
                  AddedByDefaultTypes,
                  | "search" // Handled by WithSearchState
                  | "last_click" // handled by this.drawLastClick()
                  | "summary" // handled by setupSummaryLayer
                  | "range" // handled by UserMapFeatureSwitchState
                  | "selected_element" // handled by this.drawSelectedElement
              >
        /**
         * A listing which maps the layerId onto the featureSource
         */
        const specialLayers: Record<LayersToAdd, FeatureSource> = {
            home_location: this.userRelatedState.homeLocation,
            gps_location: this.geolocation.currentUserLocation,
            gps_location_history: this.geolocation.historicalUserLocations,
            gps_track: this.geolocation.historicalUserLocationsTrack,
            current_view: this.currentView,
            favourite: this.favourites,
            geocoded_image: new StaticFeatureSource(this.geocodedImages),
        }

        // enumerate all 'normal' layers and match them with the appropriate 'special' layer - if applicable
        this.layerState.filteredLayers.forEach((flayer) => {
            this.registerSpecialLayer(flayer, specialLayers[flayer.layerDef.id])
        })
    }

    private initActorsSpecialLayers() {
        this.selectedElement.addCallback((selected) => {
            if (selected === undefined) {
                this.focusOnMap()
                this.geocodedImages.set([])
            } else {
                this.lastClickObject.clear()
            }
        })
    }
}
