import Script from "./Script"
import { LayerConfigJson } from "../src/Models/ThemeConfig/Json/LayerConfigJson"
import { existsSync, readFileSync, writeFileSync } from "fs"
import { AllSharedLayers } from "../src/Customizations/AllSharedLayers"
import { AllKnownLayoutsLazy } from "../src/Customizations/AllKnownLayouts"
import { Utils } from "../src/Utils"
import {
    MappingConfigJson,
    QuestionableTagRenderingConfigJson,
} from "../src/Models/ThemeConfig/Json/QuestionableTagRenderingConfigJson"
import { TagConfigJson } from "../src/Models/ThemeConfig/Json/TagConfigJson"
import { TagUtils } from "../src/Logic/Tags/TagUtils"
import { TagRenderingConfigJson } from "../src/Models/ThemeConfig/Json/TagRenderingConfigJson"
import { Translatable } from "../src/Models/ThemeConfig/Json/Translatable"
import * as questions from "../assets/layers/questions/questions.json"

export class GenerateFavouritesLayer extends Script {
    private readonly layers: LayerConfigJson[] = []

    constructor() {
        super("Prepares the 'favourites'-layer")
        const allThemes = new AllKnownLayoutsLazy(false).values()
        for (const theme of allThemes) {
            if (theme.hideFromOverview) {
                continue
            }
            for (const layer of theme.layers) {
                if (!layer.source) {
                    continue
                }
                if (layer.source.geojsonSource) {
                    continue
                }
                const layerConfig = AllSharedLayers.getSharedLayersConfigs().get(layer.id)
                if (!layerConfig) {
                    continue
                }
                this.layers.push(layerConfig)
            }
        }
    }

    async main(args: string[]): Promise<void> {
        console.log("Generating the favourite layer: stealing _all_ tagRenderings")
        const proto = this.readLayer("favourite/favourite.proto.json")
        this.addTagRenderings(proto)
        this.addTitle(proto)
        proto.titleIcons = this.generateTitleIcons()
        delete proto.filter
        const targetContent = JSON.stringify(proto, null, "  ")
        const path = "./assets/layers/favourite/favourite.json"
        if (existsSync(path)) {
            if (readFileSync(path, "utf8") === targetContent) {
                console.log(
                    "Already existing favourite layer is identical to the generated one, not writing"
                )
                return
            }
        }
        console.log("Written favourite layer to", path)
        writeFileSync(path, targetContent)
    }

    private sortMappings(mappings: MappingConfigJson[]): MappingConfigJson[] {
        const sortedMappings: MappingConfigJson[] = [...mappings]
        sortedMappings.sort((a, b) => {
            const aTag = TagUtils.Tag(a.if)
            const bTag = TagUtils.Tag(b.if)
            const aPop = TagUtils.GetPopularity(aTag)
            const bPop = TagUtils.GetPopularity(bTag)
            return aPop - bPop
        })

        return sortedMappings
    }

    private addTagRenderings(proto: LayerConfigJson) {
        const addedByDefault = (<{ labels: string[]; id: string }[]>questions.tagRenderings)
            .filter(
                (tr) =>
                    tr?.["labels"]?.indexOf("added_by_default") > 0 ||
                    tr?.["labels"]?.indexOf("added_by_default_top") > 0
            )
            .map((tr) => tr.id)
        const blacklistedIds = new Set([
            "images",
            "questions",
            "mapillary",
            "leftover-questions",
            "last_edit",
            "minimap",
            "move-button",
            "delete-button",
            "all-tags",
            "all_tags",
            ...addedByDefault,
        ])

        const generatedTagRenderings: (string | QuestionableTagRenderingConfigJson)[] = []
        const trPerId = new Map<
            string,
            { conditions: TagConfigJson[]; tr: QuestionableTagRenderingConfigJson }
        >()
        for (const layerConfig of this.layers) {
            if (!layerConfig.tagRenderings) {
                continue
            }
            for (const tagRendering of layerConfig.tagRenderings) {
                if (typeof tagRendering === "string") {
                    if (blacklistedIds.has(tagRendering)) {
                        continue
                    }
                    generatedTagRenderings.push(tagRendering)
                    blacklistedIds.add(tagRendering)
                    continue
                }
                if (tagRendering["builtin"]) {
                    continue
                }
                const id = tagRendering.id
                if (blacklistedIds.has(id)) {
                    continue
                }
                if (trPerId.has(id)) {
                    const old = trPerId.get(id).tr

                    // We need to figure out if this was a 'recycled' tag rendering or just happens to have the same id
                    function isSame(fieldName: string) {
                        return old[fieldName]?.["en"] === tagRendering[fieldName]?.["en"]
                    }

                    const sameQuestion = isSame("question") && isSame("render")
                    if (!sameQuestion) {
                        const newTr = <QuestionableTagRenderingConfigJson>Utils.Clone(tagRendering)
                        newTr.id = layerConfig.id + "_" + newTr.id
                        if (blacklistedIds.has(newTr.id)) {
                            continue
                        }
                        newTr.condition = {
                            and: Utils.NoNull([newTr.condition, layerConfig.source["osmTags"]]),
                        }
                        generatedTagRenderings.push(newTr)
                        blacklistedIds.add(newTr.id)
                        continue
                    }
                }
                if (!trPerId.has(id)) {
                    const newTr = <QuestionableTagRenderingConfigJson>Utils.Clone(tagRendering)
                    generatedTagRenderings.push(newTr)
                    trPerId.set(newTr.id, { tr: newTr, conditions: [] })
                }
                const conditions = trPerId.get(id).conditions
                if (tagRendering["condition"]) {
                    conditions.push({
                        and: [tagRendering["condition"], layerConfig.source["osmTags"]],
                    })
                } else {
                    conditions.push(layerConfig.source["osmTags"])
                }
            }
        }

        for (const { tr, conditions } of Array.from(trPerId.values())) {
            const optimized = TagUtils.optimzeJson({ or: conditions })
            if (optimized === true) {
                continue
            }
            if (optimized === false) {
                throw `Optimized ${TagUtils.Tag({
                    or: conditions,
                }).asHumanString()} into 'false', this is weird...`
            }
            tr.condition = optimized
        }

        const allTags: QuestionableTagRenderingConfigJson = {
            id: "all-tags",
            render: { "*": "{all_tags()}" },

            metacondition: {
                or: [
                    "__featureSwitchIsDebugging=true",
                    "mapcomplete-show_tags=full",
                    "mapcomplete-show_debug=yes",
                ],
            },
        }
        proto.tagRenderings = [
            "images",
            ...generatedTagRenderings,
            ...proto.tagRenderings,
            "questions",
            allTags,
        ]
    }

    /**
     * const titleIcons = new GenerateFavouritesLayer().generateTitleIcons()
     * JSON.stringify(titleIcons).indexOf("icons.defaults") // => -1
     * */
    private generateTitleIcons(): TagRenderingConfigJson[] {
        const iconsLibrary: Map<string, TagRenderingConfigJson[]> = new Map<
            string,
            TagRenderingConfigJson[]
        >()
        const path = "./public/assets/generated/layers/icons.json"
        if (existsSync(path)) {
            const config = <LayerConfigJson>JSON.parse(readFileSync(path, "utf8"))
            for (const tagRendering of config.tagRenderings) {
                const qtr = <QuestionableTagRenderingConfigJson>tagRendering
                const id = qtr.id
                if (id) {
                    iconsLibrary.set(id, [qtr])
                }
                for (const label of tagRendering["labels"] ?? []) {
                    if (!iconsLibrary.has(label)) {
                        iconsLibrary.set(label, [])
                    }
                    iconsLibrary.get(label).push(qtr)
                }
            }
        }
        const titleIcons: TagRenderingConfigJson[] = []
        const seenTitleIcons = new Set<string>()
        for (const layer of this.layers) {
            for (const titleIcon of layer.titleIcons) {
                if (typeof titleIcon === "string") {
                    continue
                }
                if (titleIcon["labels"]?.indexOf("defaults") >= 0) {
                    continue
                }
                if (titleIcon.id === "iconsdefaults") {
                    continue
                }

                if (titleIcon.id === "rating") {
                    if (!seenTitleIcons.has("rating")) {
                        titleIcons.unshift(...iconsLibrary.get("rating"))
                        seenTitleIcons.add("rating")
                    }
                    continue
                }
                if (seenTitleIcons.has(titleIcon.id)) {
                    continue
                }
                if (titleIcon.id === undefined) {
                    continue
                }
                seenTitleIcons.add(titleIcon.id)
                console.log("Adding title icon", titleIcon.id)
                titleIcons.push(titleIcon)
            }
        }
        titleIcons.push(...(iconsLibrary.get("defaults") ?? []))
        return titleIcons
    }

    private addTitle(proto: LayerConfigJson) {
        let mappings: MappingConfigJson[] = []
        for (const layer of this.layers) {
            const t = layer.title
            const tags: TagConfigJson = layer.source["osmTags"]
            if (!t) {
                continue
            }
            if (typeof t === "string") {
                mappings.push({ if: tags, then: t })
            } else if (t["render"] !== undefined || t["mappings"] !== undefined) {
                const tr = <TagRenderingConfigJson>t
                for (let i = 0; i < (tr.mappings ?? []).length; i++) {
                    const mapping = tr.mappings[i]
                    const optimized = TagUtils.optimzeJson({
                        and: [mapping.if, tags],
                    })
                    if (optimized === false) {
                        console.warn(
                            "The following tags yielded 'false':",
                            JSON.stringify(mapping.if),
                            JSON.stringify(tags)
                        )
                        continue
                    }
                    if (optimized === true) {
                        console.error(
                            "The following tags yielded 'false':",
                            JSON.stringify(mapping.if),
                            JSON.stringify(tags)
                        )
                        throw "Tags for title optimized to true"
                    }

                    if (!mapping.then) {
                        throw (
                            "The title has a missing 'then' for mapping " +
                            i +
                            " in layer " +
                            layer.id
                        )
                    }
                    mappings.push({
                        if: optimized,
                        then: mapping.then,
                    })
                }
                if (tr.render) {
                    mappings.push({
                        if: tags,
                        then: <Translatable>tr.render,
                    })
                }
            } else {
                mappings.push({ if: tags, then: <Record<string, string>>t })
            }
        }

        mappings = this.sortMappings(mappings)

        if (proto.title["mappings"]) {
            mappings.unshift(...proto.title["mappings"])
        }
        if (proto.title["render"]) {
            mappings.push({
                if: "id~*",
                then: proto.title["render"],
            })
        }

        for (const mapping of mappings) {
            const opt = TagUtils.optimzeJson(mapping.if)
            if (typeof opt === "boolean") {
                continue
            }
            mapping.if = opt
        }

        proto.title = {
            mappings,
        }
    }

    private readLayer(path: string): LayerConfigJson {
        try {
            return JSON.parse(readFileSync("./assets/layers/" + path, "utf8"))
        } catch (e) {
            console.error("Could not read ./assets/layers/" + path)
            throw e
        }
    }
}

new GenerateFavouritesLayer().run()
