import { SpecialVisualization } from "../SpecialVisualization"
import BaseUIElement from "../BaseUIElement"
import { UIEventSource } from "../../Logic/UIEventSource"
import FeaturePipelineState from "../../Logic/State/FeaturePipelineState"
import { VariableUiElement } from "../Base/VariableUIElement"
import { OsmTags } from "../../Models/OsmFeature"
import * as all_languages from "../../assets/language_translations.json"
import { Translation } from "../i18n/Translation"
import Combine from "../Base/Combine"
import Title from "../Base/Title"
import Lazy from "../Base/Lazy"
import { SubstitutedTranslation } from "../SubstitutedTranslation"
import List from "../Base/List"
import { AllLanguagesSelector } from "./AllLanguagesSelector"
import ChangeTagAction from "../../Logic/Osm/Actions/ChangeTagAction"
import { And } from "../../Logic/Tags/And"
import { Tag } from "../../Logic/Tags/Tag"
import { EditButton, SaveButton } from "./SaveButton"
import { FixedUiElement } from "../Base/FixedUiElement"
import Translations from "../i18n/Translations"
import Toggle from "../Input/Toggle"
import { On } from "../../Models/ThemeConfig/Conversion/Conversion"

export class LanguageElement implements SpecialVisualization {
    funcName: string = "language_chooser"

    docs: string | BaseUIElement =
        "The language element allows to show and pick all known (modern) languages. The key can be set"

    args: { name: string; defaultValue?: string; doc: string; required?: boolean }[] = [
        {
            name: "key",
            required: true,
            doc: "What key to use, e.g. `language`, `tactile_writing:braille:language`, ... If a language is supported, the language code will be appended to this key, resulting in `language:nl=yes` if nl is picked ",
        },
        {
            name: "question",
            required: true,
            doc: "What to ask if no questions are known",
        },
        {
            name: "render_list_item",
            doc: "How a single language will be shown in the list of languages. Use `{language}` to indicate the language (which it must contain).",
            defaultValue: "{language()}",
        },
        {
            name: "render_single_language",
            doc: "What will be shown if the feature only supports a single language",
            required: true,
        },
        {
            name: "render_all",
            doc: "The full rendering. Use `{list}` to show where the list of languages must come. Optional if mode=single",
            defaultValue: "{list()}",
        },
        {
            name: "no_known_languages",
            doc: "The text that is shown if no languages are known for this key. If this text is omitted, the languages will be prompted instead",
        },
        {
            name: "mode",
            doc: "If one or many languages can be selected. Should be 'multi' or 'single'",
            defaultValue: "multi",
        },
    ]

    example: `
    \`\`\`json
     {"special":
       "type": "language_chooser",
       "key": "school:language",
       "question": {"en": "What are the main (and administrative) languages spoken in this school?"},
       "render_single_language": {"en": "{language()} is spoken on this school"},
       "render_list_item": {"en": "{language()}"},
       "render_all": {"en": "The following languages are spoken here:{list()}"}
       "mode":"multi"
     }
     \`\`\`
    `

    constr(
        state: FeaturePipelineState,
        tagSource: UIEventSource<OsmTags>,
        argument: string[]
    ): BaseUIElement {
        let [key, question, item_render, single_render, all_render, on_no_known_languages, mode] =
            argument
        if (mode === undefined || mode.length == 0) {
            mode = "multi"
        }
        if (item_render === undefined || item_render.trim() === "") {
            item_render = "{language()}"
        }
        if (all_render === undefined || all_render.length == 0) {
            all_render = "{list()}"
        }
        if (mode !== "single" && mode !== "multi") {
            throw (
                "Error while calling language_chooser: mode must be either 'single' or 'multi' but it is " +
                mode
            )
        }
        if (single_render.indexOf("{language()") < 0) {
            throw (
                "Error while calling language_chooser: render_single_language must contain '{language()}' but it is " +
                single_render
            )
        }
        if (item_render.indexOf("{language()") < 0) {
            throw (
                "Error while calling language_chooser: render_list_item must contain '{language()}' but it is " +
                item_render
            )
        }
        if (all_render.indexOf("{list()") < 0) {
            throw "Error while calling language_chooser: render_all must contain '{list()}'"
        }

        const prefix = key + ":"
        const foundLanguages = tagSource.map((tags) => {
            const foundLanguages: string[] = []
            for (const k in tags) {
                const v = tags[k]
                if (v !== "yes") {
                    continue
                }
                if (k.startsWith(prefix)) {
                    foundLanguages.push(k.substring(prefix.length))
                }
            }
            return foundLanguages
        })
        const forceInputMode = new UIEventSource(false)
        const inputEl = new Lazy(() => {
            const selector = new AllLanguagesSelector({
                mode: mode === "single" ? "select-one" : "select-many",
                currentCountry: tagSource.map((tgs) => tgs["_country"]),
            })
            const cancelButton = Toggle.If(forceInputMode, () =>
                Translations.t.general.cancel
                    .Clone()
                    .SetClass("btn btn-secondary")
                    .onClick(() => forceInputMode.setData(false))
            )

            const saveButton = new SaveButton(
                selector.GetValue().map((lngs) => (lngs.length > 0 ? "true" : undefined)),
                state.osmConnection
            ).onClick(() => {
                const selectedLanguages = selector.GetValue().data
                const currentLanguages = foundLanguages.data
                const selection: Tag[] = selectedLanguages.map((ln) => new Tag(prefix + ln, "yes"))

                for (const currentLanguage of currentLanguages) {
                    if (selectedLanguages.indexOf(currentLanguage) >= 0) {
                        continue
                    }
                    // Erase language that is not spoken anymore
                    selection.push(new Tag(prefix + currentLanguage, ""))
                }

                if (state.featureSwitchIsTesting.data) {
                    for (const tag of selection) {
                        tagSource.data[tag.key] = tag.value
                    }
                    tagSource.ping()
                } else {
                    ;(state?.changes)
                        .applyAction(
                            new ChangeTagAction(
                                tagSource.data.id,
                                new And(selection),
                                tagSource.data,
                                {
                                    theme: state?.layoutToUse?.id ?? "unkown",
                                    changeType: "answer",
                                }
                            )
                        )
                        .then((_) => {
                            console.log("Tagchanges applied")
                        })
                }
                forceInputMode.setData(false)
            })

            return new Combine([
                new Title(question),
                selector,
                new Combine([cancelButton, saveButton]).SetClass("flex justify-end"),
            ]).SetClass("flex flex-col question disable-links")
        })

        const editButton = new EditButton(state.osmConnection, () => forceInputMode.setData(true))

        return new VariableUiElement(
            foundLanguages.map(
                (foundLanguages) => {
                    if (forceInputMode.data) {
                        return inputEl
                    }

                    if (foundLanguages.length === 0) {
                        // No languages found - we show the question and the input element
                        if (
                            on_no_known_languages !== undefined &&
                            on_no_known_languages.length > 0
                        ) {
                            return new Combine([on_no_known_languages, editButton]).SetClass(
                                "flex justify-end"
                            )
                        }
                        return inputEl
                    }

                    let rendered: BaseUIElement
                    if (foundLanguages.length === 1) {
                        const ln = foundLanguages[0]
                        let mapping = new Map<string, BaseUIElement>()
                        mapping.set("language", new Translation(all_languages[ln]))
                        rendered = new SubstitutedTranslation(
                            new Translation({ "*": single_render }, undefined),
                            tagSource,
                            state,
                            mapping
                        )
                    } else {
                        let mapping = new Map<string, BaseUIElement>()
                        const languagesList = new List(
                            foundLanguages.map((ln) => {
                                let mappingLn = new Map<string, BaseUIElement>()
                                mappingLn.set("language", new Translation(all_languages[ln]))
                                return new SubstitutedTranslation(
                                    new Translation({ "*": item_render }, undefined),
                                    tagSource,
                                    state,
                                    mappingLn
                                )
                            })
                        )
                        mapping.set("list", languagesList)
                        rendered = new SubstitutedTranslation(
                            new Translation({ "*": all_render }, undefined),
                            tagSource,
                            state,
                            mapping
                        )
                    }
                    return new Combine([rendered, editButton]).SetClass("flex justify-between")
                },
                [forceInputMode]
            )
        )
    }
}