import ThemeConfig, { MinimalThemeInformation } from "../Models/ThemeConfig/ThemeConfig"
import { QueryParameters } from "./Web/QueryParameters"
import { FixedUiElement } from "../UI/Base/FixedUiElement"
import { Utils } from "../Utils"
import { FixLegacyTheme } from "../Models/ThemeConfig/Conversion/LegacyJsonConvert"
import { LayerConfigJson } from "../Models/ThemeConfig/Json/LayerConfigJson"
import known_layers from "../assets/generated/known_layers.json"
import { PrepareTheme } from "../Models/ThemeConfig/Conversion/PrepareTheme"
import licenses from "../assets/generated/license_info.json"
import TagRenderingConfig from "../Models/ThemeConfig/TagRenderingConfig"
import { FixImages } from "../Models/ThemeConfig/Conversion/FixImages"
import questions from "../../public/assets/generated/layers/questions.json"
import { DoesImageExist, PrevalidateTheme } from "../Models/ThemeConfig/Conversion/Validation"
import { DesugaringContext } from "../Models/ThemeConfig/Conversion/Conversion"
import { TagRenderingConfigJson } from "../Models/ThemeConfig/Json/TagRenderingConfigJson"
import { QuestionableTagRenderingConfigJson } from "../Models/ThemeConfig/Json/QuestionableTagRenderingConfigJson"
import { ThemeConfigJson } from "../Models/ThemeConfig/Json/ThemeConfigJson"
import { ValidateThemeAndLayers } from "../Models/ThemeConfig/Conversion/ValidateThemeAndLayers"
import * as theme_overview from "../assets/generated/theme_overview.json"

export default class DetermineTheme {
    private static readonly _knownImages = new Set(Array.from(licenses).map((l) => l.path))
    private static readonly loadCustomThemeParam = QueryParameters.GetQueryParameter(
        "userlayout",
        "false",
        "If the parameter is an URL, it should point to a .json of a theme which will be loaded and used"
    )

    public static getCustomDefinition(): string {
        const layoutFromBase64 = decodeURIComponent(DetermineTheme.loadCustomThemeParam.data)

        if (layoutFromBase64.startsWith("http")) {
            return layoutFromBase64
        }

        return undefined
    }

    private static async expandRemoteLayers(
        layoutConfig: ThemeConfigJson
    ): Promise<ThemeConfigJson> {
        if (!layoutConfig.layers) {
            // This is probably a layer in 'layer-only-mode'
            return layoutConfig
        }
        for (let i = 0; i < layoutConfig.layers.length; i++) {
            const l = layoutConfig.layers[i]
            if (typeof l !== "string") {
                continue
            }
            try {
                new URL(l)
                console.log("Downloading remote layer " + l)
                layoutConfig.layers[i] = <LayerConfigJson>await Utils.downloadJson(l)
            } catch (_) {
                continue
            }
        }
        return layoutConfig
    }

    private static createConversionContext(): DesugaringContext {
        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 convertState: DesugaringContext = {
            tagRenderings: DetermineTheme.getSharedTagRenderings(),
            tagRenderingOrder: DetermineTheme.getSharedTagRenderingOrder(),
            sharedLayers: knownLayersDict,
            publicLayers: new Set<string>(),
        }
        return convertState
    }

    /**
     * Gets the correct layout for this website
     */
    public static async getTheme(): Promise<ThemeConfig | undefined> {
        const layoutFromBase64 = decodeURIComponent(DetermineTheme.loadCustomThemeParam.data)

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

        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
        const id = layoutId?.toLowerCase()
        const themes: MinimalThemeInformation[] = theme_overview.themes
        if (themes.length == 0) {
            throw "Build failed or running, no layouts are known at all"
        }
        const themeInfo = themes.find((th) => th.id === id)
        if (themeInfo === undefined) {
            const alternatives = Utils.sortedByLevenshteinDistance(
                id,
                themes.map((th) => th.id),
                (i) => i
            ).slice(0, 3)
            const msg = `No builtin map theme with name ${layoutId} exists. Perhaps you meant one of ${alternatives.join(
                ", "
            )}`
            throw msg
        }
        // Actually fetch the theme

        const config = await Utils.downloadJsonCached<ThemeConfigJson>(
            "./assets/generated/themes/" + id + ".json",
            1000 * 60 * 60 * 60
        )
        const withDefault = new PrepareTheme(this.createConversionContext()).convertStrict(config)
        return new ThemeConfig(withDefault, true)
    }

    private static getSharedTagRenderings(): Map<string, QuestionableTagRenderingConfigJson> {
        const dict = new Map<string, QuestionableTagRenderingConfigJson>()

        for (const tagRendering of questions.tagRenderings) {
            dict.set(tagRendering.id, <QuestionableTagRenderingConfigJson>tagRendering)
        }

        return dict
    }

    private static getSharedTagRenderingOrder(): string[] {
        return questions.tagRenderings.map((tr) => tr.id)
    }

    private static prepCustomTheme(json: any, sourceUrl?: string, forceId?: string): ThemeConfig {
        if (json.layers === undefined && json.tagRenderings !== undefined) {
            // We got fed a layer instead of a theme
            const layerConfig = <LayerConfigJson>json
            let icon = Utils.NoNull(
                layerConfig.pointRendering
                    .flatMap((pr) => pr.marker)
                    .map((iconSpec) => {
                        if (!iconSpec) {
                            return undefined
                        }
                        const icon = new TagRenderingConfig(<TagRenderingConfigJson>iconSpec.icon)
                            .render.txt
                        if (
                            iconSpec.color === undefined ||
                            icon.startsWith("http:") ||
                            icon.startsWith("https:")
                        ) {
                            return icon
                        }
                        const color = new TagRenderingConfig(<TagRenderingConfigJson>iconSpec.color)
                            .render.txt
                        return icon + ":" + color
                    })
            ).join(";")

            if (!icon) {
                icon = "./assets/svg/bug.svg"
            }

            json = {
                id: json.id,
                description: json.description,
                icon,
                title: json.name,
                layers: [json],
            }
        }

        json = new FixLegacyTheme().convertStrict(json)
        const raw = json

        json = new FixImages(DetermineTheme._knownImages).convertStrict(json)
        json.enableNoteImports = json.enableNoteImports ?? false
        const convertState = this.createConversionContext()
        json = new PrepareTheme(convertState).convertStrict(json)
        console.log("The layoutconfig is ", json)

        json.id = forceId ?? json.id

        {
            new PrevalidateTheme().convertStrict(json)
        }
        {
            new ValidateThemeAndLayers(
                new DoesImageExist(new Set<string>(), () => true),
                "",
                false
            ).convertStrict(json)
        }
        return new ThemeConfig(json, false, {
            definitionRaw: JSON.stringify(raw, null, "  "),
            definedAtUrl: sourceUrl,
        })
    }

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

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

        let parsed = <ThemeConfigJson>await Utils.downloadJson(link)
        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)
        parsed = await this.expandRemoteLayers(parsed)
        return DetermineTheme.prepCustomTheme(parsed, link, forcedId)
    }
}
