import LayoutConfig from "../Models/ThemeConfig/LayoutConfig"
import { QueryParameters } from "./Web/QueryParameters"
import { AllKnownLayouts } from "../Customizations/AllKnownLayouts"
import { FixedUiElement } from "../UI/Base/FixedUiElement"
import { Utils } from "../Utils"
import Combine from "../UI/Base/Combine"
import { SubtleButton } from "../UI/Base/SubtleButton"
import BaseUIElement from "../UI/BaseUIElement"
import { UIEventSource } from "./UIEventSource"
import { LocalStorageSource } from "./Web/LocalStorageSource"
import LZString from "lz-string"
import { FixLegacyTheme } from "../Models/ThemeConfig/Conversion/LegacyJsonConvert"
import { LayerConfigJson } from "../Models/ThemeConfig/Json/LayerConfigJson"
import SharedTagRenderings from "../Customizations/SharedTagRenderings"
import * as known_layers from "../assets/generated/known_layers.json"
import { PrepareTheme } from "../Models/ThemeConfig/Conversion/PrepareTheme"
import * as licenses from "../assets/generated/license_info.json"
import TagRenderingConfig from "../Models/ThemeConfig/TagRenderingConfig"
import { FixImages } from "../Models/ThemeConfig/Conversion/FixImages"
import Svg from "../Svg"
import {
    DoesImageExist,
    PrevalidateTheme,
    ValidateThemeAndLayers,
} from "../Models/ThemeConfig/Conversion/Validation"

export default class DetermineLayout {
    private static readonly _knownImages = new Set(Array.from(licenses).map((l) => l.path))

    /**
     * Gets the correct layout for this website
     */
    public static async GetLayout(): Promise<LayoutConfig> {
        const loadCustomThemeParam = QueryParameters.GetQueryParameter(
            "userlayout",
            "false",
            "If not 'false', a custom (non-official) theme is loaded. This custom layout can be done in multiple ways: \n\n- The hash of the URL contains a base64-encoded .json-file containing the theme definition\n- The hash of the URL contains a lz-compressed .json-file, as generated by the custom theme generator\n- The parameter itself is an URL, in which case that URL will be downloaded. It should point to a .json of a theme"
        )
        const layoutFromBase64 = decodeURIComponent(loadCustomThemeParam.data)

        if (layoutFromBase64.startsWith("http")) {
            return await DetermineLayout.LoadRemoteTheme(layoutFromBase64)
        }

        if (layoutFromBase64 !== "false") {
            // We have to load something from the hash (or from disk)
            return DetermineLayout.LoadLayoutFromHash(loadCustomThemeParam)
        }

        let layoutId: string = undefined

        const path = window.location.pathname.split("/").slice(-1)[0]
        if (path !== "theme.html" && path !== "") {
            layoutId = path
            if (path.endsWith(".html")) {
                layoutId = path.substr(0, path.length - 5)
            }
            console.log("Using layout", layoutId)
        }
        layoutId = QueryParameters.GetQueryParameter(
            "layout",
            layoutId,
            "The layout to load into MapComplete"
        ).data
        return AllKnownLayouts.allKnownLayouts.get(layoutId?.toLowerCase())
    }

    public static LoadLayoutFromHash(userLayoutParam: UIEventSource<string>): LayoutConfig | null {
        let hash = location.hash.substr(1)
        let json: any

        try {
            // layoutFromBase64 contains the name of the theme. This is partly to do tracking with goat counter
            const dedicatedHashFromLocalStorage = LocalStorageSource.Get(
                "user-layout-" + userLayoutParam.data?.replace(" ", "_")
            )
            if (dedicatedHashFromLocalStorage.data?.length < 10) {
                dedicatedHashFromLocalStorage.setData(undefined)
            }

            const hashFromLocalStorage = LocalStorageSource.Get("last-loaded-user-layout")
            if (hash.length < 10) {
                hash = dedicatedHashFromLocalStorage.data ?? hashFromLocalStorage.data
            } else {
                console.log("Saving hash to local storage")
                hashFromLocalStorage.setData(hash)
                dedicatedHashFromLocalStorage.setData(hash)
            }

            try {
                json = JSON.parse(atob(hash))
            } catch (e) {
                // We try to decode with lz-string
                try {
                    json = JSON.parse(Utils.UnMinify(LZString.decompressFromBase64(hash)))
                } catch (e) {
                    console.error(e)
                    DetermineLayout.ShowErrorOnCustomTheme(
                        "Could not decode the hash",
                        new FixedUiElement("Not a valid (LZ-compressed) JSON")
                    )
                    return null
                }
            }

            const layoutToUse = DetermineLayout.prepCustomTheme(json)
            userLayoutParam.setData(layoutToUse.id)
            return layoutToUse
        } catch (e) {
            console.error(e)
            if (hash === undefined || hash.length < 10) {
                DetermineLayout.ShowErrorOnCustomTheme(
                    "Could not load a theme from the hash",
                    new FixedUiElement("Hash does not contain data"),
                    json
                )
            }
            this.ShowErrorOnCustomTheme("Could not parse the hash", new FixedUiElement(e), json)
            return null
        }
    }

    public static ShowErrorOnCustomTheme(
        intro: string = "Error: could not parse the custom layout:",
        error: BaseUIElement,
        json?: any
    ) {
        new Combine([
            intro,
            error.SetClass("alert"),
            new SubtleButton(Svg.back_svg(), "Go back to the theme overview", {
                url: window.location.protocol + "//" + window.location.host + "/index.html",
                newTab: false,
            }),
            json !== undefined
                ? new SubtleButton(Svg.download_svg(), "Download the JSON file").onClick(() => {
                      Utils.offerContentsAsDownloadableFile(
                          JSON.stringify(json, null, "  "),
                          "theme_definition.json"
                      )
                  })
                : undefined,
        ])
            .SetClass("flex flex-col clickable")
            .AttachTo("centermessage")
    }

    private static prepCustomTheme(json: any, sourceUrl?: string, forceId?: string): LayoutConfig {
        if (json.layers === undefined && json.tagRenderings !== undefined) {
            const iconTr = json.mapRendering.map((mr) => mr.icon).find((icon) => icon !== undefined)
            const icon = new TagRenderingConfig(iconTr).render.txt
            json = {
                id: json.id,
                description: json.description,
                descriptionTail: {
                    en: "<div class='alert'>Layer only mode.</div> The loaded custom theme actually isn't a custom theme, but only contains a layer.",
                },
                icon,
                title: json.name,
                layers: [json],
            }
        }

        const knownLayersDict = new Map<string, LayerConfigJson>()
        for (const key in known_layers.layers) {
            const layer = known_layers.layers[key]
            knownLayersDict.set(layer.id, <LayerConfigJson>layer)
        }
        const converState = {
            tagRenderings: SharedTagRenderings.SharedTagRenderingJson,
            sharedLayers: knownLayersDict,
            publicLayers: new Set<string>(),
        }
        json = new FixLegacyTheme().convertStrict(json, "While loading a dynamic theme")
        const raw = json

        json = new FixImages(DetermineLayout._knownImages).convertStrict(
            json,
            "While fixing the images"
        )
        json.enableNoteImports = json.enableNoteImports ?? false
        json = new PrepareTheme(converState).convertStrict(json, "While preparing a dynamic theme")
        console.log("The layoutconfig is ", json)

        json.id = forceId ?? json.id

        {
            let { errors } = new PrevalidateTheme().convert(json, "validation")
            if (errors.length > 0) {
                throw "Detected errors: " + errors.join("\n")
            }
        }
        {
            let { errors } = new ValidateThemeAndLayers(
                new DoesImageExist(new Set<string>(), (_) => true),
                "",
                false,
                SharedTagRenderings.SharedTagRendering
            ).convert(json, "validation")
            if (errors.length > 0) {
                throw "Detected errors: " + errors.join("\n")
            }
        }
        return new LayoutConfig(json, false, {
            definitionRaw: JSON.stringify(raw, null, "  "),
            definedAtUrl: sourceUrl,
        })
    }

    private static async LoadRemoteTheme(link: string): Promise<LayoutConfig | null> {
        console.log("Downloading map theme from ", link)

        new FixedUiElement(`Downloading the theme from the <a href="${link}">link</a>...`).AttachTo(
            "centermessage"
        )

        try {
            let parsed = await Utils.downloadJson(link)
            try {
                let forcedId = parsed.id
                const url = new URL(link)
                if (!(url.hostname === "localhost" || url.hostname === "127.0.0.1")) {
                    forcedId = link
                }
                console.log("Loaded remote link:", link)
                return DetermineLayout.prepCustomTheme(parsed, link, forcedId)
            } catch (e) {
                console.error(e)
                DetermineLayout.ShowErrorOnCustomTheme(
                    `<a href="${link}">${link}</a> is invalid:`,
                    new FixedUiElement(e),
                    parsed
                )
                return null
            }
        } catch (e) {
            console.error(e)
            DetermineLayout.ShowErrorOnCustomTheme(
                `<a href="${link}">${link}</a> is invalid - probably not found or invalid JSON:`,
                new FixedUiElement(e)
            )
            return null
        }
    }
}