import FeaturePipelineState from "../../Logic/State/FeaturePipelineState"
import BaseUIElement from "../BaseUIElement"
import { Stores, UIEventSource } from "../../Logic/UIEventSource"
import { DefaultGuiState } from "../DefaultGuiState"
import { SubtleButton } from "../Base/SubtleButton"
import Img from "../Base/Img"
import { FixedUiElement } from "../Base/FixedUiElement"
import Combine from "../Base/Combine"
import Link from "../Base/Link"
import { SubstitutedTranslation } from "../SubstitutedTranslation"
import { Utils } from "../../Utils"
import Minimap from "../Base/Minimap"
import ShowDataLayer from "../ShowDataLayer/ShowDataLayer"
import StaticFeatureSource from "../../Logic/FeatureSource/Sources/StaticFeatureSource"
import { VariableUiElement } from "../Base/VariableUIElement"
import Loading from "../Base/Loading"
import { OsmConnection } from "../../Logic/Osm/OsmConnection"
import Translations from "../i18n/Translations"
import LayoutConfig from "../../Models/ThemeConfig/LayoutConfig"
import { Changes } from "../../Logic/Osm/Changes"
import { UIElement } from "../UIElement"
import FilteredLayer from "../../Models/FilteredLayer"
import TagRenderingConfig from "../../Models/ThemeConfig/TagRenderingConfig"
import Lazy from "../Base/Lazy"
import List from "../Base/List"
import { SpecialVisualization } from "../SpecialVisualization"

export interface AutoAction extends SpecialVisualization {
    supportsAutoAction: boolean

    applyActionOn(
        state: {
            layoutToUse: LayoutConfig
            changes: Changes
        },
        tagSource: UIEventSource<any>,
        argument: string[]
    ): Promise<void>
}

class ApplyButton extends UIElement {
    private readonly icon: string
    private readonly text: string
    private readonly targetTagRendering: string
    private readonly target_layer_id: string
    private readonly state: FeaturePipelineState
    private readonly target_feature_ids: string[]
    private readonly buttonState = new UIEventSource<
        "idle" | "running" | "done" | { error: string }
    >("idle")
    private readonly layer: FilteredLayer
    private readonly tagRenderingConfig: TagRenderingConfig

    constructor(
        state: FeaturePipelineState,
        target_feature_ids: string[],
        options: {
            target_layer_id: string
            targetTagRendering: string
            text: string
            icon: string
        }
    ) {
        super()
        this.state = state
        this.target_feature_ids = target_feature_ids
        this.target_layer_id = options.target_layer_id
        this.targetTagRendering = options.targetTagRendering
        this.text = options.text
        this.icon = options.icon
        this.layer = this.state.filteredLayers.data.find(
            (l) => l.layerDef.id === this.target_layer_id
        )
        this.tagRenderingConfig = this.layer.layerDef.tagRenderings.find(
            (tr) => tr.id === this.targetTagRendering
        )
    }

    protected InnerRender(): string | BaseUIElement {
        if (this.target_feature_ids.length === 0) {
            return new FixedUiElement("No elements found to perform action")
        }

        if (this.tagRenderingConfig === undefined) {
            return new FixedUiElement(
                "Target tagrendering " + this.targetTagRendering + " not found"
            ).SetClass("alert")
        }
        const self = this
        const button = new SubtleButton(new Img(this.icon), this.text).onClick(() => {
            this.buttonState.setData("running")
            window.setTimeout(() => {
                self.Run()
            }, 50)
        })

        const explanation = new Combine([
            "The following objects will be updated: ",
            ...this.target_feature_ids.map(
                (id) => new Combine([new Link(id, "https:/  /openstreetmap.org/" + id, true), ", "])
            ),
        ]).SetClass("subtle")

        const previewMap = Minimap.createMiniMap({
            allowMoving: false,
            background: this.state.backgroundLayer,
            addLayerControl: true,
        }).SetClass("h-48")

        const features = this.target_feature_ids.map((id) =>
            this.state.allElements.ContainingFeatures.get(id)
        )

        new ShowDataLayer({
            leafletMap: previewMap.leafletMap,
            zoomToFeatures: true,
            features: StaticFeatureSource.fromGeojson(features),
            state: this.state,
            layerToShow: this.layer.layerDef,
        })

        return new VariableUiElement(
            this.buttonState.map((st) => {
                if (st === "idle") {
                    return new Combine([button, previewMap, explanation])
                }
                if (st === "done") {
                    return new FixedUiElement("All done!").SetClass("thanks")
                }
                if (st === "running") {
                    return new Loading("Applying changes...")
                }
                const error = st.error
                return new Combine([
                    new FixedUiElement("Something went wrong...").SetClass("alert"),
                    new FixedUiElement(error).SetClass("subtle"),
                ]).SetClass("flex flex-col")
            })
        )
    }

    private async Run() {
        try {
            console.log("Applying auto-action on " + this.target_feature_ids.length + " features")

            for (const targetFeatureId of this.target_feature_ids) {
                const featureTags = this.state.allElements.getEventSourceById(targetFeatureId)
                const rendering = this.tagRenderingConfig.GetRenderValue(featureTags.data).txt
                const specialRenderings = Utils.NoNull(
                    SubstitutedTranslation.ExtractSpecialComponents(rendering).map((x) => x.special)
                ).filter((v) => v.func["supportsAutoAction"] === true)

                if (specialRenderings.length == 0) {
                    console.warn(
                        "AutoApply: feature " +
                            targetFeatureId +
                            " got a rendering without supported auto actions:",
                        rendering
                    )
                }

                for (const specialRendering of specialRenderings) {
                    const action = <AutoAction>specialRendering.func
                    await action.applyActionOn(this.state, featureTags, specialRendering.args)
                }
            }
            console.log("Flushing changes...")
            await this.state.changes.flushChanges("Auto button")
            this.buttonState.setData("done")
        } catch (e) {
            console.error("Error while running autoApply: ", e)
            this.buttonState.setData({ error: e })
        }
    }
}

export default class AutoApplyButton implements SpecialVisualization {
    public readonly docs: BaseUIElement
    public readonly funcName: string = "auto_apply"
    public readonly args: {
        name: string
        defaultValue?: string
        doc: string
        required?: boolean
    }[] = [
        {
            name: "target_layer",
            doc: "The layer that the target features will reside in",
            required: true,
        },
        {
            name: "target_feature_ids",
            doc: "The key, of which the value contains a list of ids",
            required: true,
        },
        {
            name: "tag_rendering_id",
            doc: "The ID of the tagRendering containing the autoAction. This tagrendering will be calculated. The embedded actions will be executed",
            required: true,
        },
        {
            name: "text",
            doc: "The text to show on the button",
            required: true,
        },
        {
            name: "icon",
            doc: "The icon to show on the button",
            defaultValue: "./assets/svg/robot.svg",
        },
    ]

    constructor(allSpecialVisualisations: SpecialVisualization[]) {
        this.docs = AutoApplyButton.generateDocs(
            allSpecialVisualisations
                .filter((sv) => sv["supportsAutoAction"] === true)
                .map((sv) => sv.funcName)
        )
    }

    private static generateDocs(supportedActions: string[]) {
        return new Combine([
            "A button to run many actions for many features at once.",
            "To effectively use this button, you'll need some ingredients:",
            new List([
                "A target layer with features for which an action is defined in a tag rendering. The following special visualisations support an autoAction: " +
                    supportedActions.join(", "),
                "A host feature to place the auto-action on. This can be a big outline (such as a city). Another good option for this is the layer ",
                new Link("current_view", "./BuiltinLayers.md#current_view"),
                "Then, use a calculated tag on the host feature to determine the overlapping object ids",
                "At last, add this component",
            ]),
        ])
    }

    constr(
        state: FeaturePipelineState,
        tagSource: UIEventSource<any>,
        argument: string[],
        guistate: DefaultGuiState
    ): BaseUIElement {
        try {
            if (
                !state.layoutToUse.official &&
                !(
                    state.featureSwitchIsTesting.data ||
                    state.osmConnection._oauth_config.url ===
                        OsmConnection.oauth_configs["osm-test"].url
                )
            ) {
                const t = Translations.t.general.add.import
                return new Combine([
                    new FixedUiElement(
                        "The auto-apply button is only available in official themes (or in testing mode)"
                    ).SetClass("alert"),
                    t.howToTest,
                ])
            }

            const target_layer_id = argument[0]
            const targetTagRendering = argument[2]
            const text = argument[3]
            const icon = argument[4]
            const options = {
                target_layer_id,
                targetTagRendering,
                text,
                icon,
            }

            return new Lazy(() => {
                const to_parse = new UIEventSource(undefined)
                // Very ugly hack: read the value every 500ms
                Stores.Chronic(500, () => to_parse.data === undefined).addCallback(() => {
                    const applicable = tagSource.data[argument[1]]
                    to_parse.setData(applicable)
                })

                const loading = new Loading("Gathering which elements support auto-apply... ")
                return new VariableUiElement(
                    to_parse.map((ids) => {
                        if (ids === undefined) {
                            return loading
                        }

                        return new ApplyButton(state, JSON.parse(ids), options)
                    })
                )
            })
        } catch (e) {
            return new FixedUiElement(
                "Could not generate a auto_apply-button for key " + argument[0] + " due to " + e
            ).SetClass("alert")
        }
    }

    getLayerDependencies(args: string[]): string[] {
        return [args[0]]
    }
}