import Toggle from "../Input/Toggle"
import Svg from "../../Svg"
import { UIEventSource } from "../../Logic/UIEventSource"
import { SubtleButton } from "../Base/SubtleButton"
import Minimap from "../Base/Minimap"
import ShowDataLayer from "../ShowDataLayer/ShowDataLayer"
import { GeoOperations } from "../../Logic/GeoOperations"
import { LeafletMouseEvent } from "leaflet"
import Combine from "../Base/Combine"
import { Button } from "../Base/Button"
import Translations from "../i18n/Translations"
import SplitAction from "../../Logic/Osm/Actions/SplitAction"
import Title from "../Base/Title"
import StaticFeatureSource from "../../Logic/FeatureSource/Sources/StaticFeatureSource"
import ShowDataMultiLayer from "../ShowDataLayer/ShowDataMultiLayer"
import LayerConfig from "../../Models/ThemeConfig/LayerConfig"
import { BBox } from "../../Logic/BBox"
import * as split_point from "../../assets/layers/split_point/split_point.json"
import { OsmConnection } from "../../Logic/Osm/OsmConnection"
import { Changes } from "../../Logic/Osm/Changes"
import LayoutConfig from "../../Models/ThemeConfig/LayoutConfig"
import { ElementStorage } from "../../Logic/ElementStorage"
import BaseLayer from "../../Models/BaseLayer"
import FilteredLayer from "../../Models/FilteredLayer"
import BaseUIElement from "../BaseUIElement"
import { VariableUiElement } from "../Base/VariableUIElement"

export default class SplitRoadWizard extends Combine {
    // @ts-ignore
    private static splitLayerStyling = new LayerConfig(
        split_point,
        "(BUILTIN) SplitRoadWizard.ts",
        true
    )

    public dialogIsOpened: UIEventSource<boolean>

    /**
     * A UI Element used for splitting roads
     *
     * @param id: The id of the road to remove
     * @param state: the state of the application
     */
    constructor(
        id: string,
        state: {
            filteredLayers: UIEventSource<FilteredLayer[]>
            backgroundLayer: UIEventSource<BaseLayer>
            featureSwitchIsTesting: UIEventSource<boolean>
            featureSwitchIsDebugging: UIEventSource<boolean>
            featureSwitchShowAllQuestions: UIEventSource<boolean>
            osmConnection: OsmConnection
            featureSwitchUserbadge: UIEventSource<boolean>
            changes: Changes
            layoutToUse: LayoutConfig
            allElements: ElementStorage
        }
    ) {
        const t = Translations.t.split

        // Contains the points on the road that are selected to split on - contains geojson points with extra properties such as 'location' with the distance along the linestring
        const splitPoints = new UIEventSource<{ feature: any; freshness: Date }[]>([])

        const hasBeenSplit = new UIEventSource(false)

        // Toggle variable between show split button and map
        const splitClicked = new UIEventSource<boolean>(false)

        const leafletMap = new UIEventSource<BaseUIElement>(
            SplitRoadWizard.setupMapComponent(id, splitPoints, state)
        )

        // Toggle between splitmap
        const splitButton = new SubtleButton(
            Svg.scissors_ui().SetStyle("height: 1.5rem; width: auto"),
            new Toggle(
                t.splitAgain.Clone().SetClass("text-lg font-bold"),
                t.inviteToSplit.Clone().SetClass("text-lg font-bold"),
                hasBeenSplit
            )
        )
        splitButton.onClick(() => {
            splitClicked.setData(true)
        })

        // Only show the splitButton if logged in, else show login prompt
        const loginBtn = t.loginToSplit
            .Clone()
            .onClick(() => state.osmConnection.AttemptLogin())
            .SetClass("login-button-friendly")
        const splitToggle = new Toggle(splitButton, loginBtn, state.osmConnection.isLoggedIn)

        // Save button
        const saveButton = new Button(t.split.Clone(), async () => {
            hasBeenSplit.setData(true)
            splitClicked.setData(false)
            const splitAction = new SplitAction(
                id,
                splitPoints.data.map((ff) => ff.feature.geometry.coordinates),
                {
                    theme: state?.layoutToUse?.id,
                },
                5,
                (coordinates) => {
                    state.allElements.ContainingFeatures.get(id).geometry["coordinates"] =
                        coordinates
                }
            )
            await state.changes.applyAction(splitAction)
            // We throw away the old map and splitpoints, and create a new map from scratch
            splitPoints.setData([])
            leafletMap.setData(SplitRoadWizard.setupMapComponent(id, splitPoints, state))
        })

        saveButton.SetClass("btn btn-primary mr-3")
        const disabledSaveButton = new Button("Split", undefined)
        disabledSaveButton.SetClass("btn btn-disabled mr-3")
        // Only show the save button if there are split points defined
        const saveToggle = new Toggle(
            disabledSaveButton,
            saveButton,
            splitPoints.map((data) => data.length === 0)
        )

        const cancelButton = Translations.t.general.cancel
            .Clone() // Not using Button() element to prevent full width button
            .SetClass("btn btn-secondary mr-3")
            .onClick(() => {
                splitPoints.setData([])
                splitClicked.setData(false)
            })

        cancelButton.SetClass("btn btn-secondary block")

        const splitTitle = new Title(t.splitTitle)

        const mapView = new Combine([
            splitTitle,
            new VariableUiElement(leafletMap),
            new Combine([cancelButton, saveToggle]).SetClass("flex flex-row"),
        ])
        mapView.SetClass("question")
        super([
            Toggle.If(hasBeenSplit, () =>
                t.hasBeenSplit.Clone().SetClass("font-bold thanks block w-full")
            ),
            new Toggle(mapView, splitToggle, splitClicked),
        ])
        this.dialogIsOpened = splitClicked
    }

    private static setupMapComponent(
        id: string,
        splitPoints: UIEventSource<{ feature: any; freshness: Date }[]>,
        state: {
            filteredLayers: UIEventSource<FilteredLayer[]>
            backgroundLayer: UIEventSource<BaseLayer>
            featureSwitchIsTesting: UIEventSource<boolean>
            featureSwitchIsDebugging: UIEventSource<boolean>
            featureSwitchShowAllQuestions: UIEventSource<boolean>
            osmConnection: OsmConnection
            featureSwitchUserbadge: UIEventSource<boolean>
            changes: Changes
            layoutToUse: LayoutConfig
            allElements: ElementStorage
        }
    ): BaseUIElement {
        // Load the road with given id on the minimap
        const roadElement = state.allElements.ContainingFeatures.get(id)

        // Minimap on which you can select the points to be splitted
        const miniMap = Minimap.createMiniMap({
            background: state.backgroundLayer,
            allowMoving: true,
            leafletOptions: {
                minZoom: 14,
            },
        })
        miniMap.SetStyle("width: 100%; height: 24rem").SetClass("rounded-xl overflow-hidden")

        miniMap.installBounds(BBox.get(roadElement).pad(0.25), false)

        // Define how a cut is displayed on the map

        // Datalayer displaying the road and the cut points (if any)
        new ShowDataMultiLayer({
            features: StaticFeatureSource.fromGeojson([roadElement]),
            layers: state.filteredLayers,
            leafletMap: miniMap.leafletMap,
            zoomToFeatures: true,
            state,
        })

        new ShowDataLayer({
            features: new StaticFeatureSource(splitPoints),
            leafletMap: miniMap.leafletMap,
            zoomToFeatures: false,
            layerToShow: SplitRoadWizard.splitLayerStyling,
            state,
        })
        /**
         * Handles a click on the overleaf map.
         * Finds the closest intersection with the road and adds a point there, ready to confirm the cut.
         * @param coordinates Clicked location, [lon, lat]
         */
        function onMapClick(coordinates) {
            // First, we check if there is another, already existing point nearby
            const points = splitPoints.data
                .map((f, i) => [f.feature, i])
                .filter(
                    (p) => GeoOperations.distanceBetween(p[0].geometry.coordinates, coordinates) < 5
                )
                .map((p) => p[1])
                .sort((a, b) => a - b)
                .reverse(/*Copy/derived list, inplace reverse is fine*/)
            if (points.length > 0) {
                for (const point of points) {
                    splitPoints.data.splice(point, 1)
                }
                splitPoints.ping()
                return
            }

            // Get nearest point on the road
            const pointOnRoad = GeoOperations.nearestPoint(<any>roadElement, coordinates) // pointOnRoad is a geojson

            // Update point properties to let it match the layer
            pointOnRoad.properties["_split_point"] = "yes"

            // Add it to the list of all points and notify observers
            splitPoints.data.push({ feature: pointOnRoad, freshness: new Date() }) // show the point on the data layer
            splitPoints.ping() // not updated using .setData, so manually ping observers
        }

        // When clicked, pass clicked location coordinates to onMapClick function
        miniMap.leafletMap.addCallbackAndRunD((leafletMap) =>
            leafletMap.on("click", (mouseEvent: LeafletMouseEvent) => {
                onMapClick([mouseEvent.latlng.lng, mouseEvent.latlng.lat])
            })
        )
        return miniMap
    }
}